How do I remove the "Wrapper" element and buttons "back" and "menu" from main.storyboard? but to keep loading bar
I tried to delete part of the code but it gives errors.
I will be very happy if someone helps me. Thank you very much in advance for your help.
Below you will find the code I use
ViewController.swift:
import WebKit
class ViewController: UIViewController {
// MARK: Outlets
#IBOutlet weak var leftButton: UIBarButtonItem!
#IBOutlet weak var rightButton: UIBarButtonItem!
#IBOutlet weak var webViewContainer: UIView!
#IBOutlet weak var offlineView: UIView!
#IBOutlet weak var offlineIcon: UIImageView!
#IBOutlet weak var offlineButton: UIButton!
#IBOutlet weak var activityIndicatorView: UIView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
// MARK: Globals
var webView: WKWebView!
var tempView: WKWebView!
var progressBar : UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.title = appTitle
setupApp()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// UI Actions
// handle back press
#IBAction func onLeftButtonClick(_ sender: Any) {
if (webView.canGoBack) {
webView.goBack()
// fix a glitch, as the above seems to trigger observeValue -> WKWebView.isLoading
activityIndicatorView.isHidden = true
activityIndicator.stopAnimating()
} else {
// exit app
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
}
}
// open menu in page, or fire alternate function on large screens
#IBAction func onRightButtonClick(_ sender: Any) {
if (changeMenuButtonOnWideScreens && isWideScreen()) {
webView.evaluateJavaScript(alternateRightButtonJavascript, completionHandler: nil)
} else {
webView.evaluateJavaScript(menuButtonJavascript, completionHandler: nil)
}
}
// reload page from offline screen
#IBAction func onOfflineButtonClick(_ sender: Any) {
offlineView.isHidden = true
webViewContainer.isHidden = false
loadAppUrl()
}
// Observers for updating UI
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == #keyPath(WKWebView.isLoading)) {
// show activity indicator
/*
// this causes troubles when swiping back and forward.
// having this disabled means that the activity view is only shown on the startup of the app.
// ...which is fair enough.
if (webView.isLoading) {
activityIndicatorView.isHidden = false
activityIndicator.startAnimating()
}
*/
}
if (keyPath == #keyPath(WKWebView.estimatedProgress)) {
progressBar.progress = Float(webView.estimatedProgress)
rightButton.isEnabled = (webView.estimatedProgress == 1)
}
}
// Initialize WKWebView
func setupWebView() {
// set up webview
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: webViewContainer.frame.width, height: webViewContainer.frame.height))
webView.navigationDelegate = self
webView.uiDelegate = self
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webViewContainer.addSubview(webView)
// settings
webView.allowsBackForwardNavigationGestures = true
webView.configuration.preferences.javaScriptEnabled = true
if #available(iOS 10.0, *) {
webView.configuration.ignoresViewportScaleLimits = false
}
// user agent
if #available(iOS 9.0, *) {
if (useCustomUserAgent) {
webView.customUserAgent = customUserAgent
}
if (useUserAgentPostfix) {
if (useCustomUserAgent) {
webView.customUserAgent = customUserAgent + " " + userAgentPostfix
} else {
tempView = WKWebView(frame: .zero)
tempView.evaluateJavaScript("navigator.userAgent", completionHandler: { (result, error) in
if let resultObject = result {
self.webView.customUserAgent = (String(describing: resultObject) + " " + userAgentPostfix)
self.tempView = nil
}
})
}
}
webView.configuration.applicationNameForUserAgent = ""
}
// bounces
webView.scrollView.bounces = enableBounceWhenScrolling
// init observers
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.isLoading), options: NSKeyValueObservingOptions.new, context: nil)
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: NSKeyValueObservingOptions.new, context: nil)
}
// Initialize UI elements
// call after WebView has been initialized
func setupUI() {
// leftButton.isEnabled = false
// progress bar
progressBar = UIProgressView(frame: CGRect(x: 0, y: 0, width: webViewContainer.frame.width, height: 40))
progressBar.autoresizingMask = [.flexibleWidth]
progressBar.progress = 0.0
progressBar.tintColor = progressBarColor
webView.addSubview(progressBar)
// activity indicator
activityIndicator.color = activityIndicatorColor
activityIndicator.startAnimating()
// offline container
offlineIcon.tintColor = offlineIconColor
offlineButton.tintColor = buttonColor
offlineView.isHidden = true
// setup navigation bar
if (forceLargeTitle) {
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode.always
}
}
if (useLightStatusBarStyle) {
self.navigationController?.navigationBar.barStyle = UIBarStyle.black
}
// handle menu button changes
/// set default
rightButton.title = menuButtonTitle
/// update if necessary
updateRightButtonTitle(invert: false)
/// create callback for device rotation
let deviceRotationCallback : (Notification) -> Void = { _ in
// this fires BEFORE the UI is updated, so we check for the opposite orientation,
// if it's not the initial setup
self.updateRightButtonTitle(invert: true)
}
/// listen for device rotation
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: .main, using: deviceRotationCallback)
/*
// #DEBUG: test offline view
offlineView.isHidden = false
webViewContainer.isHidden = true
*/
}
// load startpage
func loadAppUrl() {
let urlRequest = URLRequest(url: webAppUrl!)
webView.load(urlRequest)
}
// Initialize App and start loading
func setupApp() {
setupWebView()
setupUI()
loadAppUrl()
}
// Cleanup
deinit {
webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.isLoading))
webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}
// Helper method to determine wide screen width
func isWideScreen() -> Bool {
// this considers device orientation too.
if (UIScreen.main.bounds.width >= wideScreenMinWidth) {
return true
} else {
return false
}
}
// UI Helper method to update right button text according to available screen width
func updateRightButtonTitle(invert: Bool) {
if (changeMenuButtonOnWideScreens) {
// first, check if device is wide enough to
if (UIScreen.main.fixedCoordinateSpace.bounds.height < wideScreenMinWidth) {
// long side of the screen is not long enough, don't need to update
return
}
// second, check if both portrait and landscape would fit
if (UIScreen.main.fixedCoordinateSpace.bounds.height >= wideScreenMinWidth
&& UIScreen.main.fixedCoordinateSpace.bounds.width >= wideScreenMinWidth) {
// both orientations are considered "wide"
rightButton.title = alternateRightButtonTitle
return
}
// if we land here, check the current screen width.
// we need to flip it around in some cases though, as our callback is triggered before the UI is updated
let changeToAlternateTitle = invert
? !isWideScreen()
: isWideScreen()
if (changeToAlternateTitle) {
rightButton.title = alternateRightButtonTitle
} else {
rightButton.title = menuButtonTitle
}
}
}
}
// WebView Event Listeners
extension ViewController: WKNavigationDelegate {
// didFinish
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// set title
if (changeAppTitleToPageTitle) {
navigationItem.title = webView.title
}
// hide progress bar after initial load
progressBar.isHidden = true
// hide activity indicator
activityIndicatorView.isHidden = true
activityIndicator.stopAnimating()
}
// didFailProvisionalNavigation
// == we are offline / page not available
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
// show offline screen
offlineView.isHidden = false
webViewContainer.isHidden = true
}
}
// WebView additional handlers
extension ViewController: WKUIDelegate {
// handle links opening in new tabs
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if (navigationAction.targetFrame == nil) {
webView.load(navigationAction.request)
}
return nil
}
// restrict navigation to target host, open external links in 3rd party apps
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let requestUrl = navigationAction.request.url {
if let requestHost = requestUrl.host {
if (requestHost.range(of: allowedOrigin) != nil ) {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
if (UIApplication.shared.canOpenURL(requestUrl)) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(requestUrl)
} else {
// Fallback on earlier versions
UIApplication.shared.openURL(requestUrl)
}
}
}
} else {
decisionHandler(.cancel)
}
}
}
}
AppDelegate.swift:
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Change Navigation style
UINavigationBar.appearance().barTintColor = navigationBarColor
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: navigationTitleColor]
UIBarButtonItem.appearance().tintColor = navigationButtonColor
if #available(iOS 11.0, *) {
UINavigationBar.appearance().largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: navigationTitleColor]
}
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
These are called Navigation buttons and, If you hide them, Apple will reject your WebView app with the 4.2 minimum functionality, But either way, to do so- Paste this below viewDidLoad():
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated) // Hide
}
More info 👉 How to hide a navigation bar from first ViewController in Swift?
Related
EDIT2
here is a MWC containing 2 classes with only the SFSafariViewController for opening an url embedded. What I get on screen is a blank page
The files are also here https://github.com/camillegallet/testSwiftSafariEmbeded
AppDelegate
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
View controller
import UIKit
import WebKit
import SafariServices
class ViewController: UIViewController {
#IBOutlet weak var KronosWebsite: WKWebView!
override func loadView() {
KronosWebsite = WKWebView()
self.view = KronosWebsite
}
override func viewDidLoad() {
super.viewDidLoad()
openGoogle()
}
func openGoogle(){
let url2=URL(string: "http://www.google.com")
let web = SFSafariViewController(url: url2!)
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
do{
topController.present(web, animated: true, completion: nil)
}catch let error {
DispatchQueue.main.async {
print("ERROR \(error)")
}
}
}
}
}
I've this old function in a library
public func authorizeSafariEmbedded(from controller: UIViewController, at url: URL) throws -> SFSafariViewController {
safariViewDelegate = OAuth2SFViewControllerDelegate(authorizer: self)
let web = SFSafariViewController(url: url)
web.title = oauth2.authConfig.ui.title
web.delegate = safariViewDelegate as! OAuth2SFViewControllerDelegate
if #available(iOS 10.0, *), let barTint = oauth2.authConfig.ui.barTintColor {
web.preferredBarTintColor = barTint
}
if #available(iOS 10.0, *), let tint = oauth2.authConfig.ui.controlTintColor {
web.preferredControlTintColor = tint
}
web.modalPresentationStyle = oauth2.authConfig.ui.modalPresentationStyle
willPresent(viewController: web, in: nil)
controller.present(web, animated: true, completion: nil)
return web
}
I call this function with those lines
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
do{
let web = try authorizer.authorizeSafariEmbedded(from: topController,at: url!)
}catch let error {
DispatchQueue.main.async {
print("ERROR \(error)")
}
}
}
But I keep having a blank page, I've not found a correct working solution on the web
I've also tested with
let url=URL(string: "http://www.google.com")
even this don't works
public func authorizeSafariEmbedded(from controller: UIViewController, at url: URL) throws -> SFSafariViewController {
let web = SFSafariViewController(url: url)
web.title = oauth2.authConfig.ui.title
controller.present(web, animated: true, completion: nil)
return web
}
Thanks in advance
Here what I've done to make it works
import UIKit
import WebKit
import SafariServices
class ViewController: UIViewController {
var safariVC = SFSafariViewController(url: URL(string: "https://apple.com")!)
#IBOutlet weak var KronosWebsite: WKWebView!
override func loadView() {
KronosWebsite = WKWebView()
self.view = KronosWebsite
}
func addViewControllerAsChildViewController() {
addChild(safariVC)
self.view.addSubview(safariVC.view)
safariVC.didMove(toParent: self)
self.setUpConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
addViewControllerAsChildViewController()
}
func setUpConstraints() {
self.safariVC.view.translatesAutoresizingMaskIntoConstraints = false
self.safariVC.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 30).isActive = true
self.safariVC.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -30).isActive = true
self.safariVC.view.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
self.safariVC.view.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -30).isActive = true
}
}
I am trying to authenticate an iOS app with the Spotify API using OAuth2.
For this I am using OAuthSwift.
When my application loads, I am redirected to Spotify, I can log in and allow my app access to my account.
When I am redirected back to my app however, the WebView is dismissed, however immediately re opens on the previous page, dismissed itself and re opens.
This continues in a loop indefinitely.
I wondered if this is something todo with having my initAuthFlow function called in viewDidAppear, however moving this to viewDidLoad complains about
Warning: Attempt to present <OAuthKeyChainApp.WKWebViewController: 0x7fb42b505160> on <OAuthKeyChainApp.HomeController: 0x7fb42b50cf30> whose view is not in the window hierarchy!
and the controller is never presented.
HomeController.swift
class HomeController: OAuthViewController {
let oauthSwift = OAuth2Swift(
consumerKey: "xxxxxx",
consumerSecret: "xxxxxx",
authorizeUrl: "https://accounts.spotify.com/en/authorize",
accessTokenUrl: "https://accounts.spotify.com/api/token",
responseType: "code"
)
lazy var internalWebViewController: WKWebViewController = {
let controller = WKWebViewController()
controller.view = UIView(frame: UIScreen.main.bounds)
controller.loadView()
controller.viewDidLoad()
return controller
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .purple
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
initAuthFlow()
}
fileprivate func initAuthFlow() -> Void {
oauthSwift.authorizeURLHandler = internalWebViewController
guard let callbackURL = URL(string: "oauthkeychainapp://oauthkeychain-callback") else { return }
oauthSwift.authorize(
withCallbackURL: callbackURL,
scope: "user-library-modify",
state: generateState(withLength: 20),
success: { (credential, response, params) in
print(credential)
}) { (error) in
print(error.localizedDescription)
}
}
}
extension HomeController: OAuthWebViewControllerDelegate {
func oauthWebViewControllerDidPresent() { }
func oauthWebViewControllerDidDismiss() { }
func oauthWebViewControllerWillAppear() { }
func oauthWebViewControllerDidAppear() { }
func oauthWebViewControllerWillDisappear() { }
func oauthWebViewControllerDidDisappear() { oauthSwift.cancel() }
}
WKWebViewController.swift
import UIKit
import WebKit
import OAuthSwift
class WKWebViewController: OAuthWebViewController {
var webView: WKWebView!
var targetURL: URL?
override func viewDidLoad() {
super.viewDidLoad()
}
override func handle(_ url: URL) {
targetURL = url
super.handle(url)
loadAddressURL()
}
func loadAddressURL() {
guard let url = targetURL else { return }
let req = URLRequest(url: url)
self.webView?.load(req)
}
}
extension WKWebViewController: WKUIDelegate, WKNavigationDelegate {
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.allowsBackForwardNavigationGestures = true
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("loaded")
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// Check for OAuth Callback
if let url = navigationAction.request.url, url.scheme == "oauthkeychainapp" {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
self.dismiss(animated: true, completion: nil)
decisionHandler(.cancel)
return
}
// Restrict URL's a user can access
if let host = navigationAction.request.url?.host {
if host.contains("spotify") {
decisionHandler(.allow)
return
} else {
// open link outside of our app
UIApplication.shared.open(navigationAction.request.url!)
decisionHandler(.cancel)
return
}
}
decisionHandler(.cancel)
}
}
You aren't doing anything to change the state of your application. Because of this initAuthFlow is being called again, Spotify I assume has a valid session for you, so the controller is dismissed and the cycle repeats.
In the success closure of your oauthSwift.authorize call you should put the tokens into the KeyChain or somewhere secure and ensure initAuthFlow is only called when that state is invalid.
So on my first screen of my iOS app I have a “Login” “SignUp” and a “SignUp With Facebook” buttons. The first two buttons link to their own view controllers just fine, and once logged in the simulator will automatically log them in with the:
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("autoSegue", sender: self)
} else {
Code that you can see at the bottom of the full block of code below. However the Facebook signup I want to transition to a separate view controller where I can show them their profile pic, capture the data on Parse, have them enter a user name, then segue to the same view controller that the SignUp and Login go to – autoSegue. I have all the code on that view controller already written out, but my problem is that when they click the signup button for Facebook, it takes them through the autoSegue and not the fbSignup segue (the one that links to where I want to capture the FB data). Both segues are linked directly from the view controller (not the buttons themselves), and I receive no build errors. I appreciate any help.
Thanks!
Full code:
import UIKit
import Parse
import MediaPlayer
import FBSDKCoreKit
class ViewController: UIViewController {
#IBOutlet var loginAlpha: UIButton!
#IBOutlet var signupAlpha: UIButton!
var avPlayer: AVPlayer!
var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false
#IBAction func facebookSignup(sender: AnyObject) {
let permissions = ["public_profile"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) { (user: PFUser?, error: NSError?) -> Void in
if let error = error {
print(error)
} else {
if let user = user {
self.performSegueWithIdentifier("fbSignup", sender: self)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// code for background video
let theURL = NSBundle.mainBundle().URLForResource("test", withExtension: "mp4")
avPlayer = AVPlayer(URL: theURL!)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
avPlayer.volume = 0
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None
avPlayerLayer.frame = view.layer.bounds
view.backgroundColor = UIColor.clearColor();
view.layer.insertSublayer(avPlayerLayer, atIndex: 0)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "playerItemDidReachEnd:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: avPlayer.currentItem)
}
func playerItemDidReachEnd(notification: NSNotification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seekToTime(kCMTimeZero)
}
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("autoSegue", sender: self)
} else {
signupAlpha.alpha = 0
loginAlpha.alpha = 0
UIView.animateWithDuration(1.5, delay: 1.0, options: [], animations: { () -> Void in
self.signupAlpha.alpha = 1.0
self.loginAlpha.alpha = 1.0
}, completion: nil)
avPlayer.play()
paused = false
}
}
override func viewDidDisappear(animated: Bool) {
avPlayer.pause()
paused = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You are using Storyboards? You may have messed up on the storyboard and connected the segue to the wrong ViewController.
I can't see anything particularly wrong with your code.
I'm working on adding a new reading view to my browser app. It is another view controller, that only includes a WKWebView added as a subview with a button (and gesture) to close the view. Everything works great, but when I rotate the device, the subview isn't resized, so I have one half of the screen empty.
The WKWebView in the Reading View gets the URL of the main View Controller with a segue performed after the user taps a button on the main View Controller and that URL is stored as webpageURL.
Here is the code I used:
import UIKit
import WebKit
class ReadingViewController: UIViewController, UIGestureRecognizerDelegate, WKNavigationDelegate, WKScriptMessageHandler {
#IBOutlet weak var _closeButton: UIButton!
#IBOutlet weak var _progressView: UIProgressView!
#IBOutlet weak var _loadingErrorView: UIView!
var webpageURL: NSURL?
var _webView: WKWebView?
var _isMainFrameNavigationAction: Bool?
var _loadingTimer: NSTimer?
var _swipeFromTopRecognizer: UIScreenEdgePanGestureRecognizer?
var _panFromRightRecognizer: UIScreenEdgePanGestureRecognizer?
var _panFromLeftRecognizer: UIScreenEdgePanGestureRecognizer?
var _errorView: UIView?
var _isCurrentPageLoaded = false
var _progressTimer: NSTimer?
var _isWebViewLoading = false
override func viewDidLoad() {
super.viewDidLoad()
var contentController = WKUserContentController();
var scaleToFit = WKUserScript(source: "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);", injectionTime: WKUserScriptInjectionTime.AtDocumentStart, forMainFrameOnly: true)
contentController.addUserScript(scaleToFit)
contentController.addScriptMessageHandler(self, name: "callbackHandler")
var webViewConfiguration: WKWebViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.allowsInlineMediaPlayback = true
webViewConfiguration.mediaPlaybackRequiresUserAction = false
_webView = WKWebView(frame: self.view.frame, configuration: webViewConfiguration)
self.view.addSubview(_webView!)
_webView!.navigationDelegate = self
self.view.sendSubviewToBack(_webView!)
_webView!.allowsBackForwardNavigationGestures = true
_loadingErrorView.hidden = true
_swipeFromTopRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: Selector("handleSwipeFromTop:"))
_swipeFromTopRecognizer!.edges = UIRectEdge.Top
_swipeFromTopRecognizer!.delegate = self
self.view.addGestureRecognizer(_swipeFromTopRecognizer!)
_progressView.hidden = true
var urlAsString = "\(webpageURL!)"
loadURL(urlAsString)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// UI Control Functions
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#IBAction func closeReadingView(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func closeButtonEnabled(bool:Bool) {
_closeButton.enabled = bool
}
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
if(message.name == "callbackHandler") {
println("JavaScript is sending a message \(message.body)")
}
}
// WebView Functions
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
_loadingErrorView.hidden = true
_isWebViewLoading = true
_progressView.hidden = false
_progressView.progress = 0
_progressTimer = NSTimer.scheduledTimerWithTimeInterval(0.01667, target: self, selector: "progressTimerCallback", userInfo: nil, repeats: true)
_loadingTimer = NSTimer.scheduledTimerWithTimeInterval(30, target: self, selector: "loadingTimeoutCallback", userInfo: nil, repeats: false)
}
func loadingTimeoutCallback() {
_webView?.stopLoading()
handleWebViewError()
}
func webView(webView: WKWebView, didCommitNavigation navigation: WKNavigation!) {
_isCurrentPageLoaded = true
_loadingTimer!.invalidate()
_isWebViewLoading = false
if self._webView!.URL == webpageURL! {
handleWebViewError()
println(webpageURL!)
println(self._webView!.URL!)
} else {
println("Page was loaded successfully")
println(webpageURL!)
println(self._webView!.URL!)
}
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
_isCurrentPageLoaded = true
_loadingTimer!.invalidate()
_isWebViewLoading = false
}
func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
if let newFrameLoading = _isMainFrameNavigationAction {
} else {
handleWebViewError()
}
}
func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError) {
if let newFrameLoading = _isMainFrameNavigationAction {
} else {
handleWebViewError()
}
}
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
if (navigationAction.targetFrame == nil && navigationAction.navigationType == .LinkActivated) {
_webView!.loadRequest(navigationAction.request)
}
_isMainFrameNavigationAction = navigationAction.targetFrame?.mainFrame
decisionHandler(.Allow)
}
func handleWebViewError() {
_loadingTimer!.invalidate()
_isCurrentPageLoaded = false
_isWebViewLoading = false
displayLoadingErrorMessage()
}
func progressTimerCallback() {
if (!_isWebViewLoading) {
if (_progressView.progress >= 1) {
_progressView.hidden = true
_progressTimer?.invalidate()
} else {
_progressView.progress += 0.2
}
} else {
_progressView.progress += 0.003
if (_progressView.progress >= 0.95) {
_progressView.progress = 0.95
}
}
}
func loadURL(urlString: String) {
let addrStr = httpifyString(urlString)
let readingAddr = addrStr.stringByAddingPercentEncodingForFormUrlencoded()!
let addr = NSURL(string: "http://mobilizer.instapaper.com/m?u=\(readingAddr)")
if let webAddr = addr {
let req = NSURLRequest(URL: webAddr)
_webView!.loadRequest(req)
} else {
displayLoadingErrorMessage()
}
}
func httpifyString(str: String) -> String {
let lcStr:String = (str as NSString).lowercaseString
if (count(lcStr) >= 7) {
if (lcStr.rangeOfString("http://") != nil) {
return str
} else if (lcStr.rangeOfString("https://") != nil) {
return str
}
}
return "http://"+str
}
func displayLoadingErrorMessage() {
_loadingErrorView.hidden = false
}
func handleGoBackPan(sender: UIScreenEdgePanGestureRecognizer) {
if (sender.state == .Ended) {
_webView!.goBack()
}
}
func handleGoForwardPan(sender: AnyObject) {
if (sender.state == .Ended) {
_webView!.goForward()
}
}
func handleSwipeFromTop(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition({ context in
self._webView!.frame = CGRectMake(0, 0, size.width, size.height)
}, completion: nil)
}
}
And here are some screenshots to demonstrate the issue:
This is the view after it finished loading, working correctly:
This is the view after rotating the device to landscape:
And this is the scroll location after rotation:
Using self.view = _webView makes the view resize correctly, but ignores all the views on the Storyboard (since the View's contents are being rewritten).
How can I fix this issue (without rewriting self.view)?
I managed to solve the problem by using this line of code:
self._webView!.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
:)
swift 3 version:
webView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
I'm posting an answer for Objective-C, just in case if someone comes here looking for it
[self.webView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
In fact, as WKWebView can only be added programmatically, it happens to have the autoresizing mask to constraints flag enabled by default (when adding components and constraints inside Interface Builder, it's usually disabled for you). I think the correct solution would be:
webView?.translatesAutoresizingMaskIntoConstraints = false
after adding the constraints/anchors.
I'm writing a small webBrowser in swift and when someone clicks a link inside a webView, this needs to be detected and the link is open in another view. I wanted to implement this by putting the code for the webView function inside a separate file, so that I will be able to use the function multiple times without copy pasting it. Thing is that the function does not get's called when someone clicks a link. Should I add something to the ViewController class or to the class for the webView function?
import Foundation
import UIKit
class myWebViewController: UIWebView {
let VC1 = ViewController(nibName: nil, bundle: nil)
let VC2 = secondViewController(nibName: nil, bundle: nil)
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if(navigationType == UIWebViewNavigationType.LinkClicked){
var url = (request.URL)
currentURL=url
webView.stopLoading()
if(webView.accessibilityIdentifier == "second"){
VC2.performSegueWithIdentifier("first", sender: VC2)
}
else {
VC1.performSegueWithIdentifier("second", sender: VC1)
}
// self.delegate?.didReceiveAPIResults(jsonResult)
//
// with
//
// dispatch_sync(dispatch_get_main_queue())
// {
// self.delegate?.didReceiveAPIResults(jsonResult)
// }
}
return true
}
}
Here is a version using WKWebView and swift. It opens the clicked link in safari while the navigation works normally if you use the web address.
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var window: UIWindow = {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
window.rootViewController = UINavigationController(rootViewController: ViewController())
return window
}()
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
window.makeKeyAndVisible()
return true
}
}
class ViewController: UIViewController, WKNavigationDelegate, UITextFieldDelegate {
lazy var webView:WKWebView = {
let webView = WKWebView(frame: CGRectZero)
webView.navigationDelegate = self
return webView
}()
let textField: UITextField = {
let textField = UITextField(frame: CGRectMake(0, 6, 100, 30))
textField.clearButtonMode = .WhileEditing
textField.placeholder = "Enter your url here:"
return textField
}()
override func loadView() {
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
navigationItem.titleView = textField
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
if textField.isFirstResponder(){
textField.resignFirstResponder()
loadCurrentUrl()
}
return true
}
func loadCurrentUrl(){
let url = NSURL(string: textField.text)
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
}
func webView(webView: WKWebView!, decidePolicyForNavigationAction navigationAction: WKNavigationAction!, decisionHandler: ((WKNavigationActionPolicy) -> Void)!) {
if navigationAction.navigationType == .LinkActivated{
UIApplication.sharedApplication().openURL(navigationAction.request.URL)
decisionHandler(.Cancel)
}else{
decisionHandler(.Allow)
}
}
}
First of all, you should set the delegate for the UIWebView.
Then, implement this method:
webView:shouldStartLoadWithRequest:navigationType
See the description of this method (Objective-C)
So, maybe you forgot to set the delegate. Check this ;)