The task is quite simple. With a given urlString open it when it is valid. This is what I tried:
func openURL(_ urlString: String) {
guard let url = URL(string: urlString) else {
showInvalidUrlAlert()
return
}
UIApplication.shared.open(url)
}
This work with this example: "https://www.google.de/?hl=de"
However when passing an invalid url, which is also possible in my application (for example: "asdfd") I get this error on the console but nothing happens in the app:
[default] Failed to open URL asdf: Error Domain=NSOSStatusErrorDomain Code=-50 "invalid input parameters" UserInfo={NSDebugDescription=invalid input parameters, _LSLine=252, _LSFunction=-[_LSDOpenClient openURL:options:completionHandler:]}
What is the best practice here?
You may want to use the completionHandler parameter:
func openURL(_ urlString: String) {
guard let url = URL(string: urlString) else {
showInvalidUrlAlert()
return
}
UIApplication.shared.open(url, completionHandler: { success in
if success {
print("opened")
} else {
print("failed")
// showInvalidUrlAlert()
}
})
}
Inside of guard statement, you can throw an exception created by your application instead only put a return, like this:
guard let urlString = url, !urlString.isEmpty, let url = URL(string: urlString) else {
throw ErrorEnum.invalidURL
}
With this approach, you can catch the error and send a UI Feedback for the User where it calls de func openURL.
Did the URL have a http or https scheme attached? to open a website, you must require the String to have http(s). other wise the application won't know how to handle it, since it also handles other protocols.
...supports many common schemes, including the http, https, tel, facetime, and mailto schemes...
https://developer.apple.com/documentation/uikit/uiapplication/1648685-open
Related
I have an Instagram scheduling app and I am trying to open this (see image below) in Swift 5.x. The goal is simple: save Image to Firebase, once it is time to post, notification!, user clicks on the notification and this (image below) opens up with the appropriate image/video to post. Everything works except for opening Instagram with the appropriate photo/video. I have tried this:
func postToInstagram(image: URL) {
let videoFileUrl: URL = image
var localId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
localId = request?.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
// completion handler is called on an arbitrary thread
// but since you (most likely) will perform some UI stuff
// you better move everything to the main thread.
DispatchQueue.main.async {
guard error == nil else {
// handle error
print(error)
return
}
guard let localId = localId else {
// highly unlikely that it'll be nil,
// but you should handle this error just in case
return
}
let url = URL(string: "instagram://library?LocalIdentifier=\(localId)")!
guard UIApplication.shared.canOpenURL(url) else {
// handle this error
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
})
}
and this:
func postToInstagram(image: URL, igURL: String) {
let urlStr: String = "instagram://app"
let url = URL(string: igURL)
if UIApplication.shared.canOpenURL(url!) {
print("can open")
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
To no avail. The latter code works, but only opens the Instagram app itself, which is fine, but I would like to open the View in the image below rather than Instagram's home screen. I also tried changing the URL to "instagram://share" and this works but goes to publish a regular post, whereas I want the user to decide what they want to do with their image.
This is where I want to go:
Note: For everyone who will be telling me this and whoever will wonder: Yes, my URL schemes (LSApplicationQueriesSchemes) are fine. And, just to clarify, I need to fetch the image/video from Firebase before posting it.
i want to go to a url by clicking on button. I tried using 'UISharedapplication'and also through the method below mentioned but none works. Please help.
Thanks.
#IBAction func Displayurl(_ sender: Any) {
UIApplication.shared.canOpenURL(NSURL (string: "http://www.apple.com")! as URL)
}
The issue is that UIApplication's canOpenURL() method simply returns whether a URL can be opened, and does not actually open the URL. Once you've determined whether the URL can be opened (by calling canOpenURL(), as you have done), you must then call open() on the shared UIApplication instance to actually open the URL. This is demonstrated below:
if let url = URL(string: "http://www.apple.com") {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
open() also takes an optional completionHandler argument with a single success parameter that you can choose to implement to determine if the URL was successfully opened.
canOpenURL(_:) method is used whether there is an installed app that can handle the url scheme. To open the resource of the specified URL use the open(_:options:completionHandler:) method. As for example
if let url = URL(string: "apple.com") {
if UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
}
For more info check the documentation here https://developer.apple.com/documentation/uikit/uiapplication/1622961-openurl
How can you check to see if a URL is valid in Swift 4? I'm building a simple web browser for personal use and even though I know to enter the full URL each time I'd rather get an alert instead of the app crashing if I forget.
import UIKit
import SafariServices
class MainViewController: UIViewController {
#IBOutlet weak var urlTextField: UITextField!
#IBAction func startBrowser(_ sender: Any) {
if let url = self.urlTextField.text {
let sfViewController = SFSafariViewController(url: NSURL(string: url)! as URL)
self.present(sfViewController, animated: true, completion: nil)
}
print ("Now browsing in SFSafariViewController")
}
}
For example, if I was to type in a web address without http:// or https:// the app would crash with the error 'NSInvalidArgumentException', reason: 'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported.'
Reading the comments on the accepted answer, I could see that you actually want to validate the URL, to check if it's valid before trying to open with Safari to prevent any crash.
You can use regex to validate the string(I created an extension, so on any string, you can check if it is a valid URL):
extension String {
func validateUrl () -> Bool {
let urlRegEx = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?"
return NSPredicate(format: "SELF MATCHES %#", urlRegEx).evaluate(with: self)
}
}
You're probably crashing because you're using the ! operator and not checking that it will work. Instead try:
#IBAction func startBrowser(_ sender: Any) {
if let urlString = self.urlTextField.text {
let url: URL?
if urlString.hasPrefix("http://") {
url = URL(string: urlString)
} else {
url = URL(string: "http://" + urlString)
}
if let url = url {
let sfViewController = SFSafariViewController(url: url)
self.present(sfViewController, animated: true, completion: nil)
print ("Now browsing in SFSafariViewController")
}
}
}
This should give you the idea of how to handle the different cases, but you probably want something more sophisticated which can deal with https and strips whitespace.
I'm trying to make a URLRequest but I need to add SSL key and password to the request but I haven't found any example of how to accomplish this.
This is my requests:
func requestFactory(request:URLRequest, completion:#escaping (_ data:Data?)->Void){
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, urlRequestResponse, error) in
if error != nil{
completion(data)
}
})
task.resume()
}
I'll really appreciate your help.
Swift 4
Assuming you have purchased yourself an SSL certificate, Google how to convert your SSL bundle certificate (.crt file) to .der format using OpenSSL in Terminal. Locate the .der file you created in your file system and drag it into your project folder in Xcode. Next, go to your project root and under Build Phases, click on the drop down list 'Copy Bundle Resources' and click the + button to add the .der file to the resource list.
Next you will need to make a class that implements URLSessionDelegate (in my case I called it URLSessionPinningDelegate) and when you formulate your URLSession call, you will pass in this class as the delegate.
You should have a look at how to implement SSL certificate pinning for instructions on how to implement this class. This site here has a perfect and functioning explanation of how to do that.
Below is an example of how to set up the session and task. The password will be passed in the Header of URLRequest when you call request.setValue so check out that documentation too. This should get you started once you've figured out SSL certificate pinning and have set up your backend to authenticate your user's password and also set up trust for your client-side certificate.
if let url = NSURL(string: "https://www.example.com") { // Your SSL server URL
var request = URLRequest(url: url as URL)
let password = "" // Your password value
request.setValue("Authorization", forHTTPHeaderField: password)
let session = URLSession(
configuration: URLSessionConfiguration.ephemeral,
delegate: URLSessionPinningDelegate(),
delegateQueue: nil)
With the added session and request parameters your code would look like this:
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil {
print("error: \(error!.localizedDescription): \(error!)")
} else if data != nil {
if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) {
print("Received data:\n\(str)")
} else {
print("Unable to convert data to text")
}
}
})
task.resume()
I'm having this weird issue in which a newly created URLSessionUploadTask gets cancelled instantly. I'm not sure if it's a bug with the current beta of Xcode 8.
I suspect it might be a bug because the code I'm about to post ran fine exactly once. No changes were made to it afterwards and then it simply stopped working. Yes, it literally ran once, and then it stopped working. I will post the error near the end.
I will post the code below, but first I will summarize how the logic here works.
My test, or user-exposed API (IE for use in Playgrounds or directly on apps), calls the authorize method. This authorize method will in turn call buildPOSTTask, which will construct a valid URL and return a URLSessionUploadTask to be used by the authorize method.
With that said, the code is below:
The session:
internal let urlSession = URLSession(configuration: .default)
Function to create an upload task:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data? = nil
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
The authentication function, which uses a task created by the above function:
public func authorize(withCode code: String?, completion: AccessTokenExchangeCompletionHandler) {
// I have removed a lot of irrelevant code here, such as the dictionary building code, to make this snippet shorter.
let obtainTokenTask = buildPOSTTask(onURLSession: self.urlSession, appendingPath: "auth/access_token", withPostParameters: nil, getParameters: body, httpHeaders: nil) { (data, response, error) in
if let err = error {
completion(error: err)
} else {
print("Response is \(response)")
completion(error: nil)
}
}
obtainTokenTask.resume()
}
I caught this error in a test:
let testUser = Anilist(grantType: grant, name: "Test Session")
let exp = expectation(withDescription: "Waiting for authorization")
testUser.authorize(withCode: "a valid code") { (error) in
if let er = error {
XCTFail("Authentication error: \(er.localizedDescription)")
}
exp.fulfill()
}
self.waitForExpectations(withTimeout: 5) { (err) in
if let error = err {
XCTFail(error.localizedDescription)
}
}
It always fails instantly with this error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED}
Here's a few things to keep in mind:
The URL used by the session is valid.
All credentials are valid.
It fails instantly with a "cancelled" error, that simply did not happen before. I am not cancelling the task anywhere, so it's being cancelled by the system.
It also fails on Playgrounds with indefinite execution enabled. This is not limited to my tests.
Here's a list of things I have tried:
Because I suspect this is a bug, I first tried to clean my project, delete derived data, and reset all simulators. None of them worked.
Even went as far restarting my Mac...
Under the small suspicion that the upload task was getting deallocated due to it not having any strong pointers, and in turn calling cancel, I also rewrote authorize to return the task created by buildPOSTTask and assigned it to a variable in my test. The task was still getting cancelled.
Things I have yet to try (but I will accept any other ideas as I work through these):
Run it on a physical device. Currently downloading iOS 10 on an iPad as this is an iOS 10 project. EDIT: I just tried and it's not possible to do this.
I'm out of ideas of what to try. The generated logs don't seem to have any useful info.
EDIT:
I have decided to just post the entire project here. The thing will be open source anyway when it is finished, and the API credentials I got are for a test app.
ALCKit
After struggling non-stop with this for 6 days, and after googling non-stop for a solution, I'm really happy to say I have finally figured it out.
Turns out that, for whatever mysterious reason, the from: parameter in uploadTask(with:from:completionHandler) cannot be nil. Despite the fact that the parameter is marked as an optional Data, it gets cancelled instantly when it is missing. This is probably a bug on Apple's side, and I opened a bug when I couldn't get this to work, so I will update my bug report with this new information.
With that said, everything I had to do was to update my buildPOSTTask method to account for the possibility of the passed dictionary to be nil. With that in place, it works fine now:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
} else {
postParameters = Data()
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
Are you by any chance using a third party library such as Ensighten? I had the exact same problem in XCode 8 beta (works fine in XCode 7) and all of my blocks with nil parameters were causing crashes. Turns out it was the library doing some encoding causing the issue.
For me, this was a weak reference causing the issue, so I changed
completion: { [weak self] (response: Result<ResponseType, Error>)
to
completion: { [self] (response: Result<ResponseType, Error>)