为从 Nib 或 Storyboard 生成的 WKWebView 设置自定义 WKWebViewConfiguration

Posted

技术标签:

【中文标题】为从 Nib 或 Storyboard 生成的 WKWebView 设置自定义 WKWebViewConfiguration【英文标题】:Setting a custom WKWebViewConfiguration for WKWebView generated from Nib or Storyboard 【发布时间】:2019-10-17 11:23:35 【问题描述】:

我想为我的应用使用自定义 URL 方案,但无法配置 URL 方案。

当我以编程方式创建 WKWebView 对象时(使用 initWithFrame:...),我可以指定我自己的 WKWebViewConfiguration 对象,但是当 Nib/Storyboard 中的 WKWebView 自动创建时(通过 initWithCoder:) 我无法指定配置。配置是只读的,所以事后我无法更改它。如果可能的话,我真的希望避免以编程方式创建 WkWebView。

注意:有一个与此相关的现有问题 (here),但那里接受的答案只解决了实际程序的一个子集(它涉及添加用户脚本而不是配置对象,因此无法添加自定义 URL 方案处理程序)。那里的其他答案要么对这个问题没有帮助,所以我打开了另一个问题。

更新:我想根据收到的评论在此澄清一下。

WKWebViewConfiguration 是一个“复制”属性,定义如下:

*@property (nonatomic, readonly, copy) WKWebViewConfiguration 配置;

我尝试多次打印它,但我发现它正在改变:

2019-05-30 15:02:25.724312-0700 MyTest [916:72204] 配置 = 0x101b06b50

2019-05-30 15:02:25.724499-0700 MyTest [916:72204] 配置 = 0x101a37110

使用 LLDB 得到相同的结果(每次都不同):

(lldb) 打印 [自我配置] (WKWebViewConfiguration *) $17 = 0x0000000102e0b990

(lldb) 打印 [自我配置] (WKWebViewConfiguration *) $18 = 0x0000000102f3bd40

【问题讨论】:

也许是愚蠢的评论,但是......你为什么不只是,在 awakeFromNib 方法中,读取在视图创建期间创建的配置(yourView.configuration),然后使用 setUrlSchemeHandler 呢? 问题是当读取该配置时,它是一个副本。因此,如果我向其中添加 URLSchemeHandler,它将不会被添加到与 WKWebView 关联的实际配置中。 Mmmhhh... 我很确定情况并非如此。 “复制”仅适用于 setter,而不适用于 getter(我同意“只读,复制”是“奇怪的”),至少在 Objective-C 中是这样。因此,由于该属性是只读的,因此您无法更改配置,但如果使用 getter,您将获得正确的配置,您可以在该配置上使用 setUrlSchemeHandler。只需做那个简单的测试:多次调用 getter 并记录结果。你会看到它没有改变。你不会每次都得到不同的副本。 我试过你说的,后续调用的值不同。我在上面的问题中添加了额外的细节。 奇怪。但是,我做了一些测试,确实,当视图来自故事板时,setUrlSchemeHandler 方法没有工作(尽管我的配置变量没有通过调用改变)。请参阅我发布的完整代码和建议的答案(我同意这个答案很糟糕) 【参考方案1】:

如何创建一个包含 WKWebView 的自定义视图。您可以在 Storyboard 中使用此视图来代替 WKWebView。

final class MyWebView: UIView 
    var webView: WKWebView!
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        let config = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: config)
        addSubview(webView)
    

    override func layoutSubviews() 
        super.layoutSubviews()
        webView.frame = bounds
    

要自定义 Storyboard 中的其他属性,请将 @IBInspectable 添加到包装视图并将值传递给内部配置。

下面的代码缺少 Storyboard 中的 WKWebView 所具有的一些属性,但是 可以实现类似这样的其他属性。

final class MyWebView: UIView 
    @IBInspectable var userAgent: String?
    @IBInspectable var appName: String? 
        didSet 
            config.applicationNameForUserAgent = appName
        
    
    private var types: WKDataDetectorTypes = [
        .phoneNumber,
        .link,
        .address,
        .calendarEvent,
        .trackingNumber,
        .flightNumber,
        .lookupSuggestion
    ]
    @IBInspectable var phoneNumber: Bool = true 
        didSet 
            if phoneNumber 
                types.insert(.phoneNumber)
             else 
                types.remove(.phoneNumber)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var link: Bool = true 
        didSet 
            if link 
                types.insert(.link)
             else 
                types.remove(.link)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var address: Bool = true 
        didSet 
            if address 
                types.insert(.address)
             else 
                types.remove(.address)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var calendarEvent: Bool = true 
        didSet 
            if calendarEvent 
                types.insert(.calendarEvent)
             else 
                types.remove(.calendarEvent)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var trackingNumber: Bool = true 
        didSet 
            if trackingNumber 
                types.insert(.trackingNumber)
             else 
                types.remove(.trackingNumber)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var flightNumber: Bool = true 
        didSet 
            if flightNumber 
                types.insert(.flightNumber)

             else 
                types.remove(.flightNumber)
            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var lookupSuggestion: Bool = true 
        didSet 
            if phoneNumber 
                types.insert(.lookupSuggestion)
             else 
                types.remove(.lookupSuggestion)

            
            config.dataDetectorTypes = types
        
    
    @IBInspectable var javascriptEnabled: Bool = true 
        didSet 
            config.preferences.javaScriptEnabled = javaScriptEnabled
        
    
    @IBInspectable var canAutoOpenWindows: Bool = false 
        didSet 
            config.preferences.javaScriptCanOpenWindowsAutomatically = canAutoOpenWindows
        
    

    lazy var webView: WKWebView = 
        let webView = WKWebView(frame: .zero, configuration: config)
        webView.customUserAgent = userAgent
        addSubview(webView)
        return webView
    ()
    private var config = WKWebViewConfiguration()

    override func layoutSubviews() 
        super.layoutSubviews()
        webView.frame = bounds
    

【讨论】:

AirXygène 的回答中已经提到了使用“母亲”视图来包含 WKWebView 的想法。这样做的问题是我仍然失去了故事板的大部分灵活性。例如,如果我更改这个包含视图的属性,我认为它们不会都适用于里面的 WkWebView。 @Locksleyu 我更新了我的答案,但这可能对你没有帮助。这使得改变 webview 里面的一些属性成为可能。不过,似乎不可能将容器的属性应用于 webview 的属性,例如 alphahidden。因为当你在 Storyboard 中更改某些属性时,webview 应该已经初始化,没有机会配置 WKWebViewConfiguration。 谢谢。很高兴知道这一点,但我真的很想找到一个允许使用情节提要中的所有属性的解决方案,否则情节提要的优势就会丧失。我已经知道如何以编程方式执行此操作,因此我正在寻找一种完全使用 Storyboard 的方法,尽管我猜 Apple 可能不支持它。不过我希望有一些技巧。【参考方案2】:

所以,我做了一些测试:

@interface  ViewController  ()

@property   (strong)    IBOutlet    WKWebView   *webView ;

@end

@implementation ViewController

- (void)    awakeFromNib

    static  BOOL    hasBeenDone    = NO ;   //  because otherwise, awakeFromNib is called twice

    if (!hasBeenDone)
    
        hasBeenDone = YES ;
    
        //  Create the scheme handler
        SchemeHandler           *mySchemeHandler    = [SchemeHandler new] ;
        NSLog(@"scheme handler : %lx",mySchemeHandler) ;
    
        if (!(self.webView))
        
            //  Case 1 - we don't have a web view from the StoryBoard, we need to create it
            NSLog(@"Case 1") ;

            //  Add the scheme
            WKWebViewConfiguration  *configuration      = [WKWebViewConfiguration new] ;
            [configuration setURLSchemeHandler:mySchemeHandler
                              forURLScheme:@"stef"] ;
        
            //  Create and set the web view
            self.webView                                = [[WKWebView alloc] initWithFrame:NSZeroRect
                                                                             configuration:configuration] ;
        
        else
        
            //  Case 2 - we have a web view from the story board, just set the URL scheme handler
            //  of the configuration
            NSLog(@"Case 2") ;

            WKWebViewConfiguration  *configuration      = self.webView.configuration ;
            [configuration setURLSchemeHandler:mySchemeHandler
                              forURLScheme:@"stef"] ;
        
        
        //  Log the view configuration
        NSLog(@"View configuration : %lx",self.webView.configuration) ;
        NSLog(@"URL handler for scheme : %lx",[self.webView.configuration     urlSchemeHandlerForURLScheme:@"stef"]) ;
    



- (void)    viewDidLoad

    [super viewDidLoad] ;

    //  Log the view configuration
    NSLog(@"View configuration : %lx",self.webView.configuration) ;
    NSLog(@"URL handler for scheme : %lx",[self.webView.configuration urlSchemeHandlerForURLScheme:@"stef"]) ;

    //  Start loading a URL with the scheme - this should log "start" if everything works fine
    NSURL           *url        = [NSURL URLWithString:@"stef://willIWinTheBounty?"] ;
    NSURLRequest    *urlRequest = [NSURLRequest requestWithURL:url] ;
    [self.webView loadRequest:urlRequest] ;


@end

如果您在情节提要中未设置 IBOutlet webView 的情况下运行该代码(案例 1),则该代码会创建 Web 视图,为方案配置它,一切都很好。

方案处理程序:600000008e30

案例一

查看配置:600003d0c780

方案的 URL 处理程序:600000008e30

查看配置:600003d0c780

方案的 URL 处理程序:600000008e30

方案处理程序启动

如果您使用情节提要中设置的 IBOutlet webView 运行该代码(案例 2),那么确实 setURLSchemeHandler:forURLScheme: 不起作用。 urlSchemeHandlerForURLScheme: 的日志在这种情况下返回 nil。

方案处理程序:600000005160

案例 2

查看配置:600003d08d20

方案的 URL 处理程序:0

查看配置:600003d08d20

方案的 URL 处理程序:0

请注意,不调用方案处理程序启动

原因不是您通过 getter 获得了不同的副本,因为配置日志表明它保持不变。只是尽管调用了 setURLSchemeHandler:forURLScheme,但没有设置方案处理程序。

所以我想唯一的解决方案是使用案例 1。根据您的视图设置,插入视图可能或多或少有些困难。我建议在你的故事板上有一个空的“母亲”视图,并使用以下代码:

@interface  ViewController  ()

@property   (weak)      IBOutlet    NSView      *webViewMother ;
@property   (strong)                WKWebView   *webView ;

@end


@implementation ViewController

- (void)    awakeFromNib

    static  BOOL    hasBeenDone    = NO ;   //  because otherwise, awakeFromNib is called twice

    if (!hasBeenDone)
    
        hasBeenDone = YES ;
    
        //  Create the scheme handler
        SchemeHandler           *mySchemeHandler    = [SchemeHandler new] ;

        //  Create the configuration
        WKWebViewConfiguration  *configuration      = [WKWebViewConfiguration new] ;
        [configuration setURLSchemeHandler:mySchemeHandler
                          forURLScheme:@"stef"] ;
    
        //  Create the web view at the size of its mother view
    self.webView                                = [[WKWebView alloc]     initWithFrame:self.webViewMother.frame
                                                                         configuration:configuration] ;
        [self.webViewMother addSubview:self.webView] ;

    //  Log the view configuration
    NSLog(@"View configuration : %lx",self.webView.configuration) ;
    NSLog(@"URL handler for scheme : %lx",[self.webView.configuration     urlSchemeHandlerForURLScheme:@"stef"]) ;
    



@end

对我来说工作得很好,但如果你的 web 视图有子视图,可能会很棘手。

查看配置:600003d082d0

方案的 URL 处理程序:600000004c90

查看配置:600003d082d0

方案的 URL 处理程序:600000004c90

方案处理程序启动

请注意,配置通过调用保持不变...

编辑:添加运行日志

【讨论】:

感谢您的详细解答!您对使用 #1 的建议本质上是以编程方式创建它,我指定我想在问题中避免。最后你的混合建议是一个有趣的想法,但我认为我的 WKWebView 很难做到这一点(由于子视图等),或者至少是一个混乱的解决方案。希望有人可以提供不需要以编程方式创建 WKWebView 对象的解决方案。 案例 1 和 2 不是解决方案,而是对问题所在的分析。我认为它表明问题在于 setURLSchemeHandler: forURLScheme: 行为,因为它根据谁创建了配置来颂扬不同的东西。至少,这应该由 Apple 记录,并且他们应该解释如何在没有这个的情况下解决您的问题。也许雷达会有所帮助。我唯一的解决方案建议确实是混合解决方案,我同意当你有子视图时它不是很好,这更像是一种解决方法,等待苹果提供真正的解决方案。祝你好运。 我与 Apple 讨论过这个问题,他们没有很好的解决方案或解决方法,他们说不支持。我还打开了一个雷达(尽管现在它被称为不同的系统)。我仍然希望有人可以提供更好的解决方法,所以要保留这个问题。

以上是关于为从 Nib 或 Storyboard 生成的 WKWebView 设置自定义 WKWebViewConfiguration的主要内容,如果未能解决你的问题,请参考以下文章

iOS 编程将 Storyboard 转换为 NIB

在 Nib 而不是 Storyboard 中创建 UITabBarController

如何初始化一个 Nib 是 Storyboard 的 UIViewController

使用 NIB 从 Storyboard 中定义 UITableViewCell

iOS开发——代码生成TabBar与视图切换具体解释

关于输出口和操作方法,以及sender