为从 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 的属性,例如alpha
或 hidden
。因为当你在 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的主要内容,如果未能解决你的问题,请参考以下文章
在 Nib 而不是 Storyboard 中创建 UITabBarController
如何初始化一个 Nib 是 Storyboard 的 UIViewController