I'm trying to set up DeepLinks on my webview app and it's almost done but I've ran into a final obstacle.
At the moment my code is working as follows:
I have a function declared in SceneDelegate that is triggered when the user clicks the deep link and then proceeds to "clean" the link which is only removing the "myappname://" from the start of it.
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let firstUrl = URLContexts.first?.url else {
return
}
let originalString = firstUrl.absoluteString
let desiredString = originalString.components(separatedBy: "sports://").last
g_url = desiredString
let notificationName = NSNotification.Name(rawValue: "deepLink")
NotificationCenter.default.post(name: notificationName, object: nil)
}
}
It then proceeds to store the String containing the link in a variable that is declared at the top as: var g_url: String?
And sends a notification alert that my ViewController catches and calls the function to handle the deepLink
#objc func updateUrl(){
let new_url = SceneDelegate.shared?.g_url
if(new_url != nil){
let request = URLRequest(url: URL(string: new_url!)!)
self.webView.load(request)
}
}
Where it simply loads that link to the webview.
This is where my problem lies, when it tries to load the link it throws this error:
WebPageProxy::didFailProvisionalLoadForFrame: frameID=3, isMainFrame=1, domain=WebKitErrorDomain, code=101, isMainFrame=1
The problem I think has something to do with the way the String is being encoded or handled, especially since if I use the exact same url but hard code it into the request it works with no issue.
I'm not sure what I'm doing wrong and would really appreciate some help, thank you :)
Found out it was due to an error from the deeplink simulator, RocketSim, that for some reason removed the double quotes from "https://" sending it as "https//" which when used in the webView.load() method throws an error because the URL wasn't valid.
Related
I am working on an application that requires to download a certain number of files to be able to work offline. Obviously, download tasks are preferred to be done with the app in the background. I implemented an URLSession with a background configuration following Apple's documentation available here : https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background. I also followed a tutorial on raywenderlich: https://www.raywenderlich.com/3244963-urlsession-tutorial-getting-started.
Basically, what I've done looks like this (I've made my class a Singleton but I have the same problem either way):
public final class DownloadService: NSObject {
static let shared = DownloadService()
static let identifier = "downloadService"
private var urlSession: URLSession!
var backgroundCompletionHandler: (() -> Void)? // This is attributed in the handleEventsForBackgroundURLSession delegate method in the AppDelegate
private override init() {
super.init()
let config = URLSessionConfiguration.background(withIdentifier: DownloadService.identifier)
config.isDiscretionary = true
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
}
extension DownloadService: URLSessionDelegate {
// Delegate method called when the background session is finished.
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
guard let completionHandler = self.backgroundCompletionHandler else {
Logger.fault("No completion for bg session", category: .network)
return
}
Logger.log("Complete background session", category: .network)
// This must be executed on the main thread
// Executes things such as updating the app preview in recent apps view
completionHandler()
}
}
}
extension DownloadService: URLSessionDownloadDelegate {
// Delegate method called when a download task is finished
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// Perform
guard let sourceUrl = downloadTask.originalRequest?.url else {
return
}
Logger.log("Received file: %#", sourceUrl.lastPathComponent, category:.network)
// Check and save file
saveFile(originalFileURL: sourceUrl, downloadedTo: location)
}
}
And I start the download using:
/// Download file using a previously created URLSession.
/// - parameter filename: Name of the file.
/// - parameter baseURL: URL where the files are located.
/// - parameter size: Expected filesize in Bytes.
private func download(file filename: String, from baseURL: String, size: Int64) {
guard let url = URL(string: baseURL)?.appendingPathComponent(filename) else { return }
let task = urlSession.downloadTask(with: url)
task.countOfBytesClientExpectsToSend = 0
task.countOfBytesClientExpectsToReceive = size
task.resume()
}
My problem is that everything works fine when the app is in foreground, but whenever I put the app in the background or lock the screen, I have an error saying:
Task <46648342-7D13-4D1F-96A1-FDAE4C1F8475>.<362> finished with error [22] Error Domain=NSPOSIXErrorDomain Code=22 "Invalid argument"
I have tried playing a bit with the URLSessionConfiguration, specifically the isDiscretionary parameter which is set to false by default, and it seems that setting it to true, as advised by Apple's documentation, even blocks the download from proceeding with the app in the foreground, resulting to the same error 'Invalid argument'.
I wonder if this parameter has anything to do with my problem, or if there's something I've misunderstood?
The exemple on raywenderlich provided above also works the same way, using isDiscretionary seems to make the download fail everytime.
I am using Xcode 11.3.1 with Swift 5 and targeting iOS13.
Let me know if any other information is needed and thank you for your help!
So, I was trying to do it with a simulator. Either by running from Xcode with the debugger, or by installing the app into the simulator (without the debugger since it affects the application lifecycle).
I tried to run it on a real device (iPad), and there's no sign of this error whatsoever! Setting isDiscretionary seems to work as intended so I'm not sure that this parameter was causing the issue on a simulator.
I created a quick sample app that is just a page with a button in it that when clicked launches SFSafariViewController with a URL pointing at my localhost page that I created. The localhost page just has a single link on it pointing at mytestapp://hello. I registered the mytestapp url scheme in my app settings in Xcode by adding it to the "URL Types" section. The plan was to make sure the URL scheme is working before implementing it into my main app that I am building, but nothing is happening when I click the link in the localhost page. The SFSafariViewController loads perfectly, the localhost page loads properly, I click the link and nothing happens.
I have added a simple print statement in the application(:url:options) method in app delegate, but that never gets run.
Here is the ViewController code...
import UIKit
import SafariServices
class ViewController: UIViewController, SFSafariViewControllerDelegate {
#IBOutlet weak var launchButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func launchTap(_ sender: UIButton) {
guard let url = URL(string: "http://localhost.dcom/test/proof.php") else {
print("Unable to create the URL")
return
}
let authorizationController = SFSafariViewController(url: url)
authorizationController.delegate = self
present(authorizationController, animated: true, completion: nil)
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
print("safari completed")
}
}
and the app delegate method here...
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print("app received url: \(url)")
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true), let message = components.path else {
print("Invalid URL")
return false
}
print("The message received was \(message)")
return true
}
I can't figure out why this isn't working, seems like I did everything I was supposed to, but the print lines in app delegate never get called.
Not sure if you need it or not, but just in case, my localhost page is literally just...
<?php
echo 'Here goes nothing';
Is there something that might not be working in an emulator in xcode and it has to be on an actual device for URL schemes to work? Did I miss a step somewhere? somethings else I have no idea? Any help would be really appreciated. Thank you
Here is a test project
Only change I have done is update webpage to following. Nothing wrong with PHP you have it's because I don't have PHP setup.
Also, you get callbacks in Scene Delegate's
func scene(_ scene: UIScene, openURLContexts URLContexts: Set)
<html>
<body>
Here goes nothing
</body>
</html>
I'm adding an iMessage extension target to my app. The extension is supposed to send a message that has a url attribute. The behaviour I'm expecting when a user touches the message is to open the browser using the url attribute of the message.
I have a button in my messageView which executes this code:
#IBAction func labelButton(_ sender: Any) {
let layout = MSMessageTemplateLayout()
layout.imageTitle = "iMessage Extension"
layout.caption = "Hello world!"
layout.subcaption = "Test sub"
guard let url: URL = URL(string: "https://google.com") else { return }
let message = MSMessage()
message.layout = layout
message.summaryText = "Sent Hello World message"
message.url = url
activeConversation?.insert(message, completionHandler: nil)
}
If I touch the message, it expands the MessageViewController
I have then added this:
override func didSelect(_ message: MSMessage, conversation: MSConversation) {
if let message = conversation.selectedMessage {
// message selected
// Eg. open your app:
self.extensionContext?.open(message.url!, completionHandler: nil)
}
}
And now, when I touch the message, it opens my main app but still not my browser.
I have seen on another post (where I cannot comment, thus I opened this post) that it is impossible to open in Safari but I have a news app which inserts links to articles and allows with a click on the message to open the article in a browser window, while the app is installed.
So, can someone please tell how I can proceed to force opening the link in a browser window?
Thank you very much.
Here is a trick to insert a link in a message. It does not allow to create an object that has an url attribute but just to insert a link directly which will open in the default web browser.
activeConversation?.insertText("https://google.com", completionHandler: nil)
I have published a sample on github showing how to launch a URL from inside an iMessage extension. It just uses a fixed URL but the launching code is what you need.
Copying from my readme
The obvious thing to try is self.extensionContext.open which is documented as Asks the system to open a URL on behalf of the currently running app extension.
That doesn't work. However, you can iterate back up the responder chain to find a suitable handler for the open method (actually the iMessage instance) and invoke open with that object.
This approach works for URLs which will open a local app, like settings for a camera, or for web URLs.
The main code
#IBAction public func onOpenWeb(_ sender: UIButton) {
guard let url = testUrl else {return}
// technique that works rather than self.extensionContext.open
var responder = self as UIResponder?
let handler = { (success:Bool) -> () in
if success {
os_log("Finished opening URL")
} else {
os_log("Failed to open URL")
}
}
let openSel = #selector(UIApplication.open(_:options:completionHandler:))
while (responder != nil){
if responder?.responds(to: openSel ) == true{
// cannot package up multiple args to openSel so we explicitly call it on the iMessage application instance
// found by iterating up the chain
(responder as? UIApplication)?.open(url, completionHandler:handler) // perform(openSel, with: url)
return
}
responder = responder!.next
}
}
I am fairly new to swift(1 week) and iOS programming, and my problem is that I seem to miss some basic understanding. Below you see a function that is triggered by a background notification. I can and have verified that I receive the background notification reliably and the app comes active (printout of the raw data values on the console) As long as the app is in the foreground everything is working just as expected, it gets fired, and sends a single https request. The background triggers come on a timer every minute.
Now the whole thing changes when the app enters into the background. In this case I am still getting the triggers through the notification (console printout) and I can see in the debugger the same function that works like a charm in the foreground stumbles. It still works, it still gets fired, but a data packet is sent only so often, randomly as it seems between 2 and 30 minutes.
let config = URLSessionConfiguration.background(withIdentifier: "org.x.Reporter")
class queryService {
let defaultSession = URLSession(configuration: config)
var dataTask: URLSessionDataTask?
var errorMessage = ""
func getSearchResults(baseURL: String, searchTerm: String) {
dataTask?.cancel()
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData;
config.timeoutIntervalForRequest = 10
if var urlComponents = URLComponents(string: "https://host.com/reportPosition.php") {
urlComponents.query = "\(searchTerm)"
guard let url = urlComponents.url else { return }
dataTask = defaultSession.dataTask(with: url)
}
// 7
dataTask?.resume()
}
}
Try using dataTaskWithCompletion so you can see what's going wrong in the error.
URLSession.shared.dataTask(with: URL.init(string: "")!) { (data, response, error) in
if error != nil {
// Error
}
}.resume()
https://developer.apple.com/documentation/foundation/urlsession/1410330-datatask
EDIT
What you want to do is for background you get completions via delegate call backs so when you init ur URLSession do so using the following func
URLSession.init(configuration: URLSessionConfiguration.init(), delegate: self, delegateQueue: OperationQueue.init())
https://developer.apple.com/documentation/foundation/urlsession/1411597-init
Then conform ur class to the URLSessionDelegate like so
class queryService, URLSessionDelegate {
then implement the delegate methods listed here for call backs
https://developer.apple.com/documentation/foundation/urlsessiondelegate
EDIT2
Here is good tutorial about it
https://www.raywenderlich.com/158106/urlsession-tutorial-getting-started
I have a UIWebView which when intercepts a particular URL makes a transition to a UIViewController.
When a button is clicked this viewController presents another UIViewController modally.
What's weird is that when a unwind segue is performed to the first UIViewController, it appears for a split second only to show the webView and I am fairly certain that this is the same webView as the first one as this webView "continues" from the URL it left previously and also all the variables initiated in the webView have the values they held prior to the segue.
func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()
if (NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies as? [NSHTTPCookie] != nil) || ((NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies as? [NSHTTPCookie])! != []){
ToolBox.cookieJar = (NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies as? [NSHTTPCookie])!
// ToolBox.maxIndex++
}
println(NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies as? [NSHTTPCookie])
var cookies = ToolBox.cookieJar
var headers : NSDictionary = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
var storage : NSHTTPCookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
for cookie in storage.cookies as! [NSHTTPCookie]{
storage.deleteCookie(cookie)
}
NSUserDefaults.standardUserDefaults()
request.allHTTPHeaderFields = (headers as [NSObject : AnyObject])
request.HTTPShouldHandleCookies = true
request.HTTPMethod = "POST"
var bodyData = "frontend=android"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let json:JSON = JSON(data: data)
onCompletion(json,error)
})
task.resume()
}
I set debug points in all the suspicious places, just before the viewController moves to the webView, task.resume() is called.
The webView shows up now, but the code in the viewController class is getting executed.
I am trying to understand what might have been the reason this is happening for the past couple of hours with no luck. I am pretty sure that I am not calling the webView at all, in fact it is the initial view controller and no other class calls it, so the only possible reason is that the webView is getting called implicitly by some function I am using, but I am unable tot figure it out, I am reading docs for every function but I found nothing to solve this bug.
If someone knows the reason for the bug, please share it with me.
EDIT: One more thing I think is important to mention is that from the animation of the transition from the viewController to webView , it seems as if the viewController is unwinding to webView, which is odd since the segue from webView to viewController is a show segue.
Update: I commented all the code in my viewWillAppear() and viewDidAppear() and ran the app. The webView popped up just after viewDidAppear() function got called the viewWillAppear() of webView is getting called.
Update2: Thanks for everyone who tried to solve this problem. The bug turned out to be a very stupid one.
#IBAction func unwindToList(segue : UIStoryboardSegue){
if !segue.sourceViewController.isBeingDismissed() {
segue.sourceViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
I was dismissing the souceViewController which was the very reason it went back to the webView. Sorry for posting such stupid question