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
}
}
Related
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?
I am actually using two WebViews in my iOS application. I want to preload the second WebView in my SecondViewController when launching the application. I've tried to preload the WebView using NSNotification Center but it does not work. How can I preload the WebView in the SecondViewController?
I solved a similar problem in the following way:
Preload a view controller which contains a webview and store a reference in Appdelegate.
Call self.preloadvc?.view.layoutSubviews() in AppDelegate
At any time needed you can present it.
It seems necessary to create the web view programmatically in the view controller
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
//
var preloadvc : PreloadVC? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Preloading
DispatchQueue.main.async(execute: {
self.preloadvc = PreloadVC()
//Thats the trick for preloading the view
self.preloadvc?.view.layoutSubviews()
})
return true
}
}
class PreloadVC : UIViewController, UIWebViewDelegate {
var mypreloadedView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
mypreloadedView = UIWebView(frame: view.frame)
view.addSubview(mypreloadedView)
mypreloadedView.delegate = self
if let myurl = URL(string: "https://www.google.de") {
let request = URLRequest(url: myurl)
self.mypreloadedView.loadRequest(request)
}
}
func webViewDidFinishLoad(_ webView: UIWebView)
{
print("Loaded")
}
}
Presentation anywhere :
#IBAction func ShowPreloaded(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let preloadedvc = appDelegate.preloadvc {
self.present(preloadedvc, animated: true, completion: nil)
}
}
I'm getting an error of
Value of type 'AppDelegate?' has no member 'present'
when I tried to keep my users logged in even when the app was quit.
Here are my view controllers, any idea how I'd keep my users logged in? And why am I getting this error?
I'm using Firebase in my code as my Database.
LoginViewController.swift
import UIKit
import Firebase
class LoginViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var pwField: UITextField!
func createAlert(title:String, message:String) {
let alert=UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "error" , style: UIAlertActionStyle.default, handler: { (action) in alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated:true, completion: nil)
}
/* #IBAction func emailKeyboardField(_ sender: Any) {
hideKeyboard()
}
func hideKeyboard() {
emailField.resignFirstResponder()
pwField.resignFirstResponder()
}
*/
override func viewDidLoad() {
super.viewDidLoad()
emailField.delegate = self
// Do any additional setup after loading the view.
}
func emailKeyField(_ emailField: UITextField) -> Bool {
self.view.endEditing(true)
return true
}
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
self.createAlert(title: "Error", message: "We could not locate your account. Check your email and password.")
}
if let user = user {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "usersVC")
self.present(vc, animated: true, completion: nil)
}
})
}
}
AppDelegate.swift
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var window: UIWindow?
var actIdc = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
var container: UIView!
private let auth = FIRAuth.auth()
class func instance() -> AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
func showActivityIndicator() {
if let window = window {
container = UIView()
container.frame = window.frame
container.center = window.center
container.backgroundColor = UIColor(white: 0, alpha: 0.8)
actIdc.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
actIdc.hidesWhenStopped = true
actIdc.center = CGPoint(x: container.frame.size.width / 2, y: container.frame.size.height / 2)
container.addSubview(actIdc)
window.addSubview(container)
actIdc.startAnimating()
}
}
func dismissActivityIndicatos() {
if let _ = window {
container.removeFromSuperview()
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
auth?.addStateDidChangeListener { [weak self] (_, user) in
if let user = user {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "usersVC")
self.present(vc, animated: true, completion: nil) //error on this line
// user is already logged in
} else {
// user is not logged in
}
}
FIRApp.configure()
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:.
}
}
Any and all help would be appreciated.
App delegate is not of type ViewController, hence it doesn't have a present method to show views on it. Instead what you can do is show a view using it's window like:
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeC = storyboard.instantiateViewController(withIdentifier: "YOUR_VIEWCONTROLLER_IDENTIFIER_IN_STORYBOARD") as? HomeC
if homeC != nil {
homeC!.view.frame = (self.window!.frame)
self.window!.addSubview(homeC!.view)
self.window!.bringSubview(toFront: homeC!.view)
}
I'm working on a project with custom video player, based on AVPlayer. Trying to integrate google cast. I've made integration based on google tuts: https://codelabs.developers.google.com/codelabs/cast-videos-ios/
But with conversion to swift. Everything seems to work fine, when cast, if video player opens, and there is connected device (or if I connect from panel), I form meta info for file, and it's passed to google cast - everything works fine.
But, i have strange behavior:
1) Start casting, open video, then another video, then third video.
2) Stop casting
3) Go to another video, enable casting, but it doesn't start this video. It start casting the first video I opened earlier....
I tried to find any method that clears cache or queue, but there is no.. Please, help
class VideoVC: UIViewController, UIGestureRecognizerDelegate, GCKSessionManagerListener {
var filmTitle: String!
var toPass: String!
var film: MovieDetails!
var filmDetails: Movie!
var sessionManager: GCKSessionManager?
var castSession: GCKCastSession?
var castMediaController: GCKUIMediaController?
var checkPlayed = 0
override func viewDidLoad() {
super.viewDidLoad()
sessionManager = GCKCastContext.sharedInstance().sessionManager
sessionManager?.add(self)
castMediaController = GCKUIMediaController()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let videoURL = toPass {
if let video = URL(string: videoURL) {
player = AVPlayer(url: video)
player.allowsExternalPlayback = true
player.usesExternalPlaybackWhileExternalScreenIsActive = true
playerController.player = player
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
self.view.sendSubview(toBack: playerController.view)
}
}
if isCastEnabled() {
playSelectedItemRemotely()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.replaceCurrentItem(with: nil)
}
func buildMediaInformation() -> GCKMediaInformation {
let metaData = GCKMediaMetadata(metadataType: GCKMediaMetadataType(rawValue: 1)!)
metaData.setString(filmTitle, forKey: kGCKMetadataKeyTitle)
if let imageUrl = URL(string: filmDetails.poster_cast!) {
let image = GCKImage(url: imageUrl, width: 340, height: 450)
metaData.addImage(image)
}
if let episode = film.serial_episode, let season = film.serial_season, season != "", episode != "", let title = film.title, title != "" {
let subtitle = "\(title) \(episode) серия \(season) сезон"
metaData.setString(subtitle, forKey: kGCKMetadataKeySubtitle)
}
let duration = Double(film.duration!)
let mediaInfo = GCKMediaInformation(contentID: toPass!,
streamType: GCKMediaStreamType.buffered,
contentType: film.contentType!,
metadata: metaData as GCKMediaMetadata,
streamDuration: duration,
mediaTracks: nil,
textTrackStyle: nil,
customData: nil)
print("toPass: \(toPass!)")
print("duration: \(duration)")
return mediaInfo
}
func playSelectedItemRemotely() {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
if (castSession != nil) {
castSession?.remoteMediaClient?.loadMedia(self.buildMediaInformation(), autoplay: true)
self.dismiss(animated: true, completion: nil)
}
else {
print("no castSession!")
}
}
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
playSelectedItemRemotely()
}
func sessionManager(_ sessionManager: GCKSessionManager, didResumeSession session: GCKSession) {
}
func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKSession, withError error: Error?) {
let castSession = GCKCastContext.sharedInstance().sessionManager.currentCastSession
castSession?.endAndStopCasting(true)
}
func sessionManager(_ sessionManager: GCKSessionManager, didFailToStart session: GCKSession, withError error: Error) {
Utils.showOverAnyVC("Ошибка подключения", message: "Попробуйте еще раз!")
}
func isCastEnabled() -> Bool {
switch GCKCastContext.sharedInstance().castState {
case GCKCastState.connected:
print("cast connected")
return true
case GCKCastState.connecting:
print("cast connecting")
return true
case GCKCastState.notConnected:
print("cast notConnected")
return false
case GCKCastState.noDevicesAvailable:
print("cast noDevicesAvailable")
return false
}
}}
and my appdelegate:
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let options = GCKCastOptions(receiverApplicationID: "F443E49F")
GCKCastContext.setSharedInstanceWith(options)
GCKLogger.sharedInstance().delegate = self
let appStoryboard = UIStoryboard(name: "NewMain", bundle: nil)
let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
let castContainerVC: GCKUICastContainerViewController = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
castContainerVC.miniMediaControlsItemEnabled = true
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = castContainerVC
self.window?.makeKeyAndVisible()
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
return true
}
func logMessage(_ message: String, fromFunction function: String) {
print("message: \(function)")
}}
On possible solution could be due to:
sessionManager?.add(self)
You add the delegate but at no point do you clear it. As a result, the VideoVC is never destroyed due to the retained reference from the session manager. When you reopen the VideoVC the session manager is still also accessing the delegate from the first time you loaded it.
Because of this, when the following is called:
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
This is being called in your first instance of VideoVC which now has the wrong file information.
You can monitor this by putting a print(self) into the above method and look at the memory pointer value. Check that it also matches the same memory pointer value that is called in viewDidLoad
Update
To better manage the delegate change the following method: viewDidDisappear()
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.replaceCurrentItem(with: nil)
//this stops the session manager sending callbacks to your VideoVC
sessionManager.remove(self)
}
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 ;)