Xcode: Unable to view one URL (no error messages) - ios

I posted this question earlier but it was marked as a duplicate which in my opinion it is not. Here are the details.
I have a view that I use to access 7 URLs. Just one of these URLs does not load. The URL is correct and it loads from iOS Safari. The 6 other URLs load without problem. All URLs are http. How do I debug this? There are no error messages.
The suggestion referred to when my previous question was closed as duplicate was that I change NSAppTransportSecurity to NSAllowsArbitraryLoads YES, but that was already the case in info.plist. Is there somewhere else that I need to change ATS?
override func viewDidLoad ()
{
super.viewDidLoad ()
if Reachability.isConnectedToNetwork() == false
{
showAlert ()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let returnNC = myStack.pop()
let vc = storyboard.instantiateViewControllerWithIdentifier(returnNC!)
self.presentViewController(vc, animated: true, completion: nil)
}
else {
let fullURL = defaults.objectForKey("URL") as? String
let url = NSURL(string: fullURL!)
let request = NSURLRequest(URL: url!)
myWebView.loadRequest(request)
}
}
func webView(webView: UIWebView!, didFailLoadWithError error: NSError!) {
print("Webview fail with error \(error)");
}

To track this down, you need more information. Even though the web page exists in the browser, there may be other issues (such as transient connections / 404 responses, etc.).
UIWebView's didFailLoadWithError() function doesn't return response codes, which could provide some information that can help you debug. See the answer at this related StackOverflow post. If you find anything useful, update your question.

Related

Sharing a Document with UIDocumentInteractionController

I'm trying to share a document stored in the temporary directory using UIDocumentInteractionController. Essentially, I'm using the following code:
#IBAction func createDocumentButtonClicked(_ sender: Any) {
do {
// create temporary file
let tempUrl = FileManager.default.temporaryDirectory.appendingPathComponent("fileName.txt")
try "abc".write(to: tempUrl, atomically: true, encoding: .utf8)
// share file
let documentInteractionController = UIDocumentInteractionController(url: tempUrl)
documentInteractionController!.name = "filename.txt"
documentInteractionController!.presentOptionsMenu(from: view.frame, in: view, animated: true)
} catch {
// ...
}
}
When run, this code presents the share action sheet. The log indicates some problem: Could not instantiate class NSURL. Error: Error Domain=NSCocoaErrorDomain Code=4864 "The URL archive of type “public.url” contains invalid data." UserInfo={NSDebugDescription=The URL archive of type “public.url” contains invalid data.
Selecting any of the options results in failure handling the document.
This is reduced to pretty much textbook level code, yet it is not working. What am I missing?
Update:
Added context that better emphasizes the cause of the problem (see my answer below).
It turned out to be trivial, even silly. I leave the question anyhow in case someone else stumbles over it.
I need to maintain the instance of UIDocumentInteractionController outside the button action handler that presents the controller. I will update the question to better show this problem. With a small change, it works as expected:
var documentInteractionController: UIDocumentInteractionController?
#IBAction func createDocumentButtonClicked(_ sender: Any) {
do {
// create temporary file
let tempUrl = FileManager.default.temporaryDirectory.appendingPathComponent("fileName.txt")
try "abc".write(to: tempUrl, atomically: true, encoding: .utf8)
// share file
documentInteractionController = UIDocumentInteractionController(url: tempUrl)
documentInteractionController!.name = "filename.txt"
documentInteractionController!.presentOptionsMenu(from: view.frame, in: view, animated: true)
} catch {
// ...
}
}

How to add file picker to the app on iOS 14+ and lower

I'm newbie in iOS development, so some things which I will show and ask here can be stupid and please don't be angry :) So, I need to add support of picking files from local storage in my app. This feature will be used for picking file -> encoding to Base64 and then sending to remote server. Right now I have some problems with adding this functionality to my app. I had found this tutorial and did everything what was mentioned here:
added import - import MobileCoreServices
added implementation - UIDocumentPickerDelegate
added this code scope for showing picker:
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText),String(kUTTypeContent),String(kUTTypeItem),String(kUTTypeData)], in: .import)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
and also added handler of selected file:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
}
In general file chooser appears on simulator screen, but I see warning in XCode:
'init(documentTypes:in:)' was deprecated in iOS 14.0
I visited the official guideline and here also found similar info about deprecation some method. So, how I can solve my problem with file choosing by the way which will be fully compatible with the latest iOS version. And another question - how I can then encode selected file? Right now I have an ability of file choosing and printing its location, but I need to get its data like name, content for encoding and some others. Maybe someone faced with similar problems and knows a solution? I need to add it in ordinary viewcontroller, so when I tried to add this implementation:
UIDocumentPickerViewController
I saw such error message:
Multiple inheritance from classes 'UIViewController' and 'UIDocumentPickerViewController'
I will be so pleased for any info: tutorials or advice :)
I decided to post my own solution of my problem. As I am new in ios development my answer can contain some logical problems :) Firstly I added some dialogue for choosing file type after pressing Attach button:
#IBAction func attachFile(_ sender: UIBarButtonItem) {
let attachSheet = UIAlertController(title: nil, message: "File attaching", preferredStyle: .actionSheet)
attachSheet.addAction(UIAlertAction(title: "File", style: .default,handler: { (action) in
let supportedTypes: [UTType] = [UTType.png,UTType.jpeg]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
documentPicker.shouldShowFileExtensions = true
self.present(documentPicker, animated: true, completion: nil)
}))
attachSheet.addAction(UIAlertAction(title: "Photo/Video", style: .default,handler: { (action) in
self.chooseImage()
}))
attachSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(attachSheet, animated: true, completion: nil)
}
then when a user will choose File he will be moved to ordinary directory where I handle his selection:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
var selectedFileData = [String:String]()
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file.absoluteURL)
selectedFileData["filename"] = file.lastPathComponent
selectedFileData["data"] = fileData.base64EncodedString(options: .lineLength64Characters)
}catch{
print("contents could not be loaded")
}
}
as you can see in scope above I formed special dicionary for storing data before sending it to a server. Here you can also see encoding to Base64.
When the user will press Photo/Video item in alert dialogue he will be moved to gallery for picture selecting:
func chooseImage() {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImageData = [String:String]()
guard let fileUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else { return }
print(fileUrl.lastPathComponent)
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
selectedImageData["filename"] = fileUrl.lastPathComponent
selectedImageData["data"] = pickedImage.pngData()?.base64EncodedString(options: .lineLength64Characters)
}
dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
via my method all file content will be encoded to base64 string.
P.S. Also I'm so pleased to #MaticOblak because he showed me the initial point for my research and final solution. His solution also good, but I have managed to solve my problem in way which is more convenient for my project :)
As soon as you have file URL you can use that URL to retrieve the data it contains. When you have the data you can convert it to Base64 and send it to server. You gave no information about how you will send it to server but the rest may look something like this:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
and you would use it as
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urls.forEach { sendFileWithURL($0) { <#Your code here#> } }
}
To break it down:
To get file data you can use Data(contentsOf: url). This method even works on remote files so you could for instance use an URL of an image link anywhere on internet you have access to. It is important to know that this method will pause your thread which is usually not what you want.
To avoid breaking the current thread we create a new queue using DispatchQueue(label: "DownloadingFileData." + UUID().uuidString). The name of the queue is not very important but can be useful when debugging.
When data is received we convert it to Base64 string using data.base64EncodedString() and this data can then be sent to server. You just need to fill in the TODO: part.
Retrieving your file data can have some errors. Maybe access restriction or file no longer there or no internet connection... This is handled by throwing. If the statement with try fails for any reason then the catch parts executes and you receive an error.
Since all of this is done on background thread it usually makes sense to go back to main thread. This is what the finish function does. If you do not require that you can simply remove it and have:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
completion(nil)
} catch {
completion(error)
}
}
}
There are other things to consider in this approach. For instance you can see if user selects multiple files then each of them will open its own queue and start the process. That means that if user selects multiple files it is possible that at some point many or all of them will be loaded in memory. That may take too much memory and crash your application. It is for you to decide if this approach is fine for you or you wish to serialize the process. The serialization should be very simple with queues. All you need is to have a single one:
private lazy var fileProcessingQueue: DispatchQueue = DispatchQueue(label: "DownloadingFileData.main")
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
fileProcessingQueue.async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
Now one operation will finish before another one starts. But that may only apply for getting file data and conversion to base64 string. If uploading is then done on another thread (Which usually is) then you may still have multiple ongoing requests which may contain all of the data needed to upload.

Check if URL is valid in Swift 4

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.

Trying to Open SFSafariViewController in AppDelegate via Push Notification

I am trying to open a URL using Safari when the app gets a push notification. This works successfully when the app is in the first viewcontroller. But if the app is further into the program I get an error
<SFSafariViewController: 0x10b20ae60> on <AdViewController: 0x100b14530> whose view is not in the window hierarchy!
(AdViewController is my first view controller, so it is still focused on that one. )
I have searched this site and have tried all the suggestions for this error but have come up short. Currently, my code looks like this:
if MessageUrl != nil , let url = URL(string: MessageUrl!) {
let safari = SFSafariViewController(url: url)
self.window?.rootViewController?.presentedViewController?.present(safari, animated: true, completion: nil)
}
Get top most UIViewController I used extension UIApplication in Appdeleagte from the "swift 3" answer at bottom of that page and changed my code to:
if MessageUrl != nil , let url = URL(string: MessageUrl!) {
let safari = SFSafariViewController(url: url)
UIApplication.topViewController()?.present(safari, animated: true, completion: nil)
}

fatal error: unexpectedly found nil while unwrapping an Optional value - Swift with NSURLRequest

I'm trying to make a simple web browser application in Swift to learn the language. Right now, I'm stuck on allowing the user to tap a bookmark they recently added, and having the webview load the certain url.
To decide which URL to choose, I have this in my BookmarkTVC (TableViewController with bookmarks)
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let vc = ViewController(nibName: "ViewController", bundle: nil)
vc.gotoWebsite(bookmarks[indexPath.row].url)
self.dismissViewControllerAnimated(true, completion: nil)
}
EDIT: Screenshot of BookmarksTVC: http://imgur.com/7rKcGBZ
I know 100% that the
bookmarks[indexPath.row].url
is getting the right string, because I println'd it.
Now, this is my vc.gotoWebsite function
func gotoWebsite(link: String) {
// Format Link
var formattedLink: String!
if link.hasPrefix("http://") || link.hasPrefix("https://") {
formattedLink = link
} else if link.hasPrefix("www.") {
formattedLink = "http://\(link)"
} else {
formattedLink = "http://www.\(link)"
}
// Goto formatted link
println(formattedLink)
let url = NSURL(string: formattedLink)
println(url)
let request = NSURLRequest(URL: url)
println(request)
webView.loadRequest(request)
}
On the webView.loadRequest(request) line, I get:
fatal error: unexpectedly found nil while unwrapping an Optional value
this is the breakpoint (Image): http://imgur.com/S7dMswD&4pdL4xX
even though I have println'd the request variable and got the response
<NSURLRequest: 0x7fb9c8616700> { URL: https://www.google.com/?gws_rd=ssl, headers: (null) }
Please help. Again, all I'm trying to do is get the url that is ssociated with the table view cell that the user clicked. Then, I'm using that string and calling a method from another class that goes to that URL.
I ran into this same problem. I overcame it by adding a request parameter to my UIViewController vc which displays the UIWebView:
var request: NSURLRequest? {
didSet {
if request != nil { // didSet is called even if you are setting request to nil :-(
webView?.loadRequest(request!) // do not load request if webView is nil
}
}
}
and then in the same viewController:
override func viewDidLoad() {
super.viewDidLoad()
// ...
if request != nil {
webView.loadRequest(request!)
}
}
so I can write in the calling code which instantiates the vc, instead of webView.loadRequest(),
vc.request = NSURLRequest(URL: NSURL(string: urlAsString)!)
and that will either trigger the webView to load via the didSet function if the webView has been defined by then, or, if it hasn't, will cause the webView to load in the viewDidLoad later. I don't know if that is perfect, but it works for me.

Resources