Open swift app from URL with properties and load specific VC - swift

Working on an application for iOS at the moment, I stumbled across the following problem: Users should be able to open the app with a specific URL scheme (demoapp://), passing an url as a property (demoapp://google.de).
I accomplished that with the following code:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var host: String!
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
host = url.host
print(host)
return true
}
[...]
However, afterwards, I want to show a specific ViewController (AddUrlViewController) with the link filled in the textfield urlFieldOutlet.
I can't find a way to show a different VC than the main one.

Related

How AppDelegate reference to subclass of ViewController works

I am using Swift 4 and I am wondering how having a reference to a ViewController in AppDelegate works. I have two questions which I have put in bold. I understand that AppDelegate is a delegate that has functions that get called when the app becomes foreground or background, or even when the user does the "open with" event on a file extension that my app is registered to handle and selects to open that file with my app- AppDelegate lets the app know that took place. 1) But what I don't understand is how AppDelegate should communicate with my Views to tell them that these events happened. I am wondering what the good design pattern is for doing so. My solution is to have a reference to a viewController so here is an example:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var hotSpotRISViewController = HotSpotRISViewController()
...
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
hotSpotRISViewController.setURL(theURL: url)
return true
}
}
What I don't understand is how this actually works. I thought that AppDelegate looks at Main.storyboard and starts that and that Main.storyboard has it's own reference to HotSpotRISViewController(). 2) How is it that by making a reference to HotSpotRISViewController() in the AppDelegate, that it uses this reference instead?

Deep link into macOS app is not recognized

I'm trying to implement a deep link into an macOS application, but nothing seems to work.
So far, my AppDelegate.swift contains the following
func application(app: NSApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
print("opened by link");
return true
}
I also configured the info.plist with URLSchemes beeing my bundle identifier and URLIdentifier beeing simply zst
In a simple html-file I use the following code to test the deep link
Test
My app gets opened (or becomes active when already running), but the print statement is not executed.
What am I doing wrong here?
Thanks to #Ssswift I found a solution.
Used this code:
How do you set your Cocoa application as the default web browser?
and converted it to swift with: https://objectivec2swift.com
works with Swift 3
in AppDelegate.swift added these lines
func applicationDidFinishLaunching(_ aNotification: Notification) {
var em = NSAppleEventManager.shared()
em.setEventHandler(self, andSelector: #selector(self.getUrl(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
func getUrl(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
// Get the URL
var urlStr: String = event.paramDescriptor(forKeyword: keyDirectObject)!.stringValue!
print(urlStr);
}

Updating Glance data in watchOS2.2

I'm hoping you smart people can help me as most of the data online is out of date.
I have an iPhone app that displays financial information.
I would like to present this on a watch glance screen.
I can get the app to send the dictionary of the latest information and the glance does update live if both the Glance screen and phone app are open.
I would like to know how to use the Glance screen to ask the phone app for the latest information.
The phone app will probably be closed so it would need waking up and then asked for the current information.
I'm using swift 7 and WatchOS 2.2 and IOS 9.3
A lot of information here on Stackoverflow refers to watchOS 1 so no longer works.
I look forward to your solutions.
Look into WCSession as there are different methods for sending different types of data. This implementation is sending a dictionary.
Must setup a WCSession on both watch and phone devices. AppDelegate in didFinishLaunchingWithOptions: and I use the ExtensionDelegate in its init method. Be sure to import WatchConnectivity when using WCSession. Using the AppDelegate as a WCSessionDelegate below.
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Setup session on phone
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
// WCSessionDelegate method receiving message from watch and using callback
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
// Reply with a dictionary of information based on the "message"
replyHandler(["Key": "Value"])
}
}
Setup WCSession on the watch:
// ExtensionDelegate.swift
override init() {
let session = WCSession.defaultSession()
session.activateSession()
}
Send message, consisting of a dictionary, to the phone in order to receive information in the callback:
// GlanceController.swift
WCSession.defaultSession().sendMessage(["Please give Glance data": "Value"], replyHandler:{ (response) in
// Extract data from response dictionary
}) { (error) in
// Handle error
}

XCGLogger: Ambiguous reference to member 'log'

Trying to set up XCGLogger and receiving error:
Ambiguous reference to member 'log'
I see this issue was already raised but I'm not clear on the solution..
Per the install guide added this global constant to AppDelegate.swift:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let log = XCGLogger.defaultInstance()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
log.setup(.Debug, showThreadName: true, showLogLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: nil, fileLogLevel: .Debug)
return true
}
Then in individual source files:
import XCGLogger
log.debug("A debug message")
What is the proper usage?
The issue is rather simple. If you declare log inside AppDelegate, you are making an instance variable. To access it, you will have to access it as instance variable:
(UIApplication.sharedApplication().delegate as! AppDelegate).log.debug("test")
If you want log to be accessible everywhere, you will have to make it a global constant:
In your AppDelegate, declare a global constant to the default XCGLogger instance.
let log = XCGLogger.defaultInstance()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
(there is no need to declare it in AppDelegate file, you can basically put it anywhere in your code)
or make it static:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static let log = XCGLogger()
and access it using:
AppDelegate.log.debug(...)
To explain the ambigious reference, there is a mathematical function called log, and there is also a log function in the malloc.h file. Since you are passing a String as the first parameter and neither of the two functions is a match, the compiler warns you that it does not know which of the two functions you want to use.
Also I guess it's better to create global constant file and create something like this (if you've declared log in AppDelegate):
let LOG = (UIApplication.sharedApplication().delegate as! AppDelegate).log
and then simple use LOG.error("error")

Play video from Apple TV Top Shelf

I have created a simple AppleTV project to display a number of videos by category, browsing and playing videos is working fine. It has been implemented as a client-server application using TVML and TVJS, so most of the application logic is in the Javascript files. These are statically generated in the background from dynamic content on a regular basis.
I've then added a TopShelf extension to the application which pulls in some featured videos from an API, this is also working fine, Pulls in the videos as expected.
The problem I am having is detecting and reacting to a user selecting a video from the top shelf. I have created a URL scheme exampletvapp:// which I have registered in my plist file.
I have also added the displayURL and playURL's to the TVContentItems. When selecting one of the videos, my app is launched as expected but does not actually handle the playback of that video.
In my AppDelegate file I have added print statements to print the launchOptions and also added a openUrl function, which does get called but only logs this if I close the application and select something from the top shelf quickly before the app closes and I lose the debug session.
// AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
print("App Launch: \(launchOptions)")
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let appControllerContext = TVApplicationControllerContext()
guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
fatalError("unable to create NSURL")
}
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL
if let options = launchOptions {
for (kind, value) in options {
if let kindStr = kind as? String {
appControllerContext.launchOptions[kindStr] = value
}
}
}
appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self)
return true
}
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
fatalError("unable to create NSURL")
}
let appControllerContext = TVApplicationControllerContext()
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL
for (kind, value) in options {
appControllerContext.launchOptions[kind] = value
}
appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self)
return true
}
func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool {
print("handle Url called")
}
Javascript:
// Application.js
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}js/ResourceLoader.js`,
`${options.BASEURL}js/Presenter.js`
];
console.log("options", options);
I have added a console.log to the JS file to check if the options are being passed over, but it's not logging anything. my intentions here was to create a Player object with a playlist of just the selected video from the TopShelf. but as the URL only seems to pass the ID over, It would mean that I need to make an ajax request to get the video information even though I had it in the TVContentItem already.
So my question is: Am I supposed to natively add the video from topshelf into a playlist and play it (with Swift) or pass the details on to the javascript side for it to play it. If it's the javascript is there a way to see the output from the console.log? As I dont see any which is making it quite difficult to work with.
UPDATE:
I've got a little further with this, I discovered (via the Apple Developer Forums) that you can attach Safari to the JS Context in the simulator for Apple TV via the Develop > Devices menu. I've done this and so far the URL is not being passed to the JS via the options. The url is passed to the openURL delegate function which needs to be either passed to the JS somehow or I need to natively play the video.
I'm currently investigating natively playing the video by passing all the required information in the displayURL to create a TVContentItem. I cannot see a way to pass this object to the JS from the openURL method unless I create a new context and instance of the controller (which may not even work)
I finally got this working natively. I did also manage to get it working by passing the openUrl to the JS but it seemed less reliable for me. I'll look further into that at a later point, I just needed this working for now.
The code below creates an AVPlayer and AVPlayerViewController and presents that ViewController and dismisses it when its finished. I haven't found any issues with this method so far, but I am still testing.
// AppDelegate.swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
let urlString = "http:\(url.path!)"
let url = NSURL(string: urlString)!
print("opening URL: \(url)")
let item = AVPlayerItem(URL: url)
// Need to listen to the end of the player so we can dismiss the AVPlayer
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: item)
let player = AVPlayer(playerItem: item)
player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
let playerVC = AVPlayerViewController()
playerVC.player = player
appController?.navigationController.presentViewController(playerVC, animated: true, completion: nil)
player.play()
return true
}
func playerDidFinishPlaying (note: NSNotification) {
// Dimiss the AVPlayer View Controller from the Navigation Controller
appController?.navigationController.dismissViewControllerAnimated(true, completion: nil)
}
I too just added a Top Shelf extension to my TVML-based app.
If you are using Apple's TVML Catalog example as a base, then the following should work in the AppDelegate to get the URL into the JavaScript context:
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
if url.host == nil { return true }
let urlString = url.absoluteString
NSLog(urlString) // Log to XCode
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
// Log to JS log (Safari)
evaluation.evaluateScript("console.log('url string: \(urlString)')")
// obviously we need to do something more interesting here
},
completion: {(Bool) -> Void in }
)
return true
}
My plan is to write a function in JavaScript that processes the URL and passes the results to my chosen TVML template. I have one for live shows and one for recordings, which are the two primary items I'm featuring in my Top Shelf extension.

Resources