I need to show a web browser in my app. I can't use SFSafariViewController as I need to be able to authenticate with NTLM, so I'm currently using Alamofire to download the data and then displaying it in a UIWebView.
I have got the UIWebView to display the HTML via .loadHTMLString(), however the images, CSS and Javascript files are not loading. I tried setting the baseURL to the website root, however, when I do this nothing at all loads in the UIWebView, leaving it blank with no content or errors in the console.
Alamofire.request(.GET, "https://dev-moss.stpaulscatholiccollege.co.uk/sites/Students/default.aspx").authenticate(usingCredential: credential)
.response { request, response, data, error in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// check for errors or bad responsecode
if (error == nil) && (response?.statusCode == 200) {
// load the website
let gatewayString = "\(NSString(data: data!, encoding: NSUTF8StringEncoding))"
self.gatewayWebView.loadHTMLString(String(NSString(data: data!, encoding: NSUTF8StringEncoding)!), baseURL: nil)
print(gatewayString)
}
else {
print("There was an error loading the website.")
}
})
}
Even when setting the baseURL to NSURL(string: "https://dev-moss.stpaulscatholiccollege.co.uk/") or NSURL(string: "https://dev-moss.stpaulscatholiccollege.co.uk") the print of gatewayString still spits out the website to the console however.
Your code isn't working because the base URL isn't https://dev-moss.stpaulscatholiccollege.co.uk/, it's https://dev-moss.stpaulscatholiccollege.co.uk/sites/Students/default.aspx. Try that and it should work.
Edit:
It looks like it's not working because https://dev-moss.stpaulscatholiccollege.co.uk/sites/Students/default.aspx uses HTTP Basic Authentication, which you have not provided credentials for (the UIWebView makes a separate requests for resources from your one made via Alamofire, which doesn't include your credentials.) See How to do authentication in UIWebView properly?
Related
I am making an app for my school. The school uses a Google Calendar to schedule all of their events. They would like me to implement the events from their calendar into the app.
I am using the Google Calander API. Since I will only be pulling information from one account there is no need for user authentication.
I have my API key and made sure my events on my google calendar are visible to everybody.
This is the JSON result that I am presented with upon running.
Here is the code in my HomeController
override func viewDidLoad() {
super.viewDidLoad()
//Event call from Google API
let url = NSURL(string: "https://www.googleapis.com/calendar/v3/calendars/<THE EMAIL ACCOUNT>/events?maxResults=15&key=APIKey-<MY API KEY>")
let task = URLSession.shared.dataTask(with: url! as URL) {(data, response, error) in
let dataAsNSString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print(dataAsNSString)
}
task.resume()
}
Your error is due to the either the fields query parameter has an error or is otherwise invalid. If you check the documentation, that was introduced there.
Suggested action: Because this is a permanent error, do not retry.
Read the error message instead and change your request accordingly.
I want to know how you guys handle errors when using a URLRequest in your app. How do you go about notifying your users that an error has occurred? Do you even notify your users at all? Do you try and reload the URLRequest again? Do you tell your users to close the current screen and open it again with an alert box? I have no clue.
Once there's an error, your app stops. So what do you do when this happens and you have a network issue, bad Json data?
What do you do when you get a "Bad Network Connection (The server is down)" or the URLSession comes back with an error and the internet connection is fine?
Please look at the code below and help me figure out what needs to be done when an error occurs.
let url = URL(string:"http://example/jsonFile.php")
var request = URLRequest(url:url!)
request.httpMethod = "POST"
let postingString = "id=\(id)"
request.httpBody = postingString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){(data, response, error) -> Void in
if error != nil {
print("error \(error)")
// *****
// What do you do here? Do you tell your users anything?
// *****
return
}
// Check for Error
if let urlContent = data {
do{
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: .allowFragments) as! [String: AnyObject]
print("jsonResult \(jsonResult)")
}
catch{
print("JSON serialization failed")
// *****
// What do you do here? Do you tell your users anything?
// *****
}
}
}
task.resume()
It is often a bad idea to hide the errors and do nothing (see Error Hiding Anti-Pattern). Unfortunately, handling errors is not easy and can be tedious some times. Pretty code examples quickly become "uglier" when exceptions and errors have to be handled. It takes time to learn to do it well.
In the case of network connections, you would want to wrap your requests into asynchronous methods that take a completion callback as parameter. This callback will often receive a successful result or an error, sometimes wrapped in an enum to remove ambiguity (see Result as an example).
Once you know a network request has failed, you can present that information to the user in a clean, informational but non-obstructive way. To find out what other applications do in this scenario, turn on Airplane Mode in your phone and poke around.
Here are some examples:
Apple Music
Facebook Messenger
How to disable web security in iOS WKWebView? I used command "open /Applications/Google\ Chrome.app --args --disable-web-security --allow-file-access-from-files" in mac system to open the chrome. But How to do this in WKWebView? Thankes!
It's not possible to disable web security in WKWebView - there's no preference to do so - see the iOS source code for WebKit preferences.
There is a way to allow access from file URLs, although it's not officially supported. In the source code, a preference exists, so you can set it like this:
[wkWebView.configuration.preferences setValue:#TRUE forKey:#"allowFileAccessFromFileURLs"];
This enables access to file URLs relative to the content src. e.g. if the local page is foo/bar/index.html you can access files in foo/bar/ (e.g. foo/bar/1.jpg or foo/bar/sub/2.jpg) but not outside (e.g. foo/other/3.jpg or Documents/NoCloud/4.jpg).
You can also add a custom URL scheme handler to the WKWebView and use that scheme in your web page. This custom scheme handler can then load data from any arbitrary location. It should be noted that this loads the entire file into memory, so it won't work for huge files.
let configuration = WKWebViewConfiguration()
configuration.setURLSchemeHandler(MySchemeHandler(), forURLScheme: "MyScheme")
let webView = WKWebView(frame: .zero, configuration: configuration)
Your MySchemeHandler needs to implement webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) and webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) (although the latter can be empty). Then, convert the custom URL into a file URL and load the file.
Loading the file seems to be a pain. There might be a better way, but this is what we use:
URLSession.shared.dataTask(with: fileUrl) { data, response, error in
if error != nil {
// cancel
return
}
let taskResponse: URLResponse
if #available(iOS 13, *) {
taskResponse = HTTPURLResponse(url: urlSchemeTask.request.url!, mimeType: response?.mimeType, expectedContentLength: Int(response?.expectedContentLength ?? 0), textEncodingName: response?.textEncodingName)
} else {
// The HTTPURLResponse object created above using the URLResponse constructor crashes when sent to
// urlSchemeTask.didReceive below in iOS 12. I have no idea why that is, but it DOESN'T crash if we
// instead use the HTTPURLResponse-only constructor. Similarly, it doesn't crash if we create a
// URLResponse object instead of an HTTPURLResponse object. So, if we know the mimeType, construct an
// HTTPURLResponse using the HTTPURLResponse constructor, and add the appropriate header field for that
// mime type. If we don't know the mime type, construct a URLResponse instead.
if let mimeType = response?.mimeType {
// The imodeljs code that loads approximateTerrainHeights.json requires the HTTP Content-Type header
// to be present and accurate. URLResponse doesn't have any headers. I have no idea how the
// HTTPURLResponse contstructor could fail, but just in case it does, we fall back to the
// URLResponse object.
taskResponse = HTTPURLResponse(url: urlSchemeTask.request.url!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "\(mimeType); charset=UTF-8"]) ?? URLResponse(url: urlSchemeTask.request.url!, mimeType: response?.mimeType, expectedContentLength: Int(response?.expectedContentLength ?? 0), textEncodingName: response?.textEncodingName)
} else {
taskResponse = URLResponse(url: urlSchemeTask.request.url!, mimeType: response?.mimeType, expectedContentLength: Int(response?.expectedContentLength ?? 0), textEncodingName: response?.textEncodingName)
}
}
urlSchemeTask.didReceive(taskResponse)
urlSchemeTask.didReceive(data!)
urlSchemeTask.didFinish()
}.resume()
I'm creating a html5 app, embed in UIWebView. I have delegate webview to controller, and on shouldStartLoadWithRequest, I'm looking for url pattern to open or not on external browser.
It's work perfect!
But when phone has no connection, I'm loading local html file. Because shouldStartLoadWithRequest return FALSE on first request, offline.html not loading
Anyone has do that?
Try this immediately after set path :
do {
let path = NSBundle.mainBundle().pathForResource("offline", ofType: "html", inDirectory:"offline")
let string = try String(contentsOfFile: path!, encoding:NSUTF8StringEncoding)
webView.loadHTMLString(string, baseURL: NSURL(string: "http://"))
} catch {
print(error)
}
or
let url = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("offline", ofType:"html", inDirectory: "offline")!)
let request = NSURLRequest(URL: url);
webView.loadRequest(request);
I tried it and it works.
Make sure your "offline" directory is a folder (BLUE) and not a group (YELLOW).
Hope this help.
I found the problem. I'm not triggering error -1003 (no host found)! removing switch work perfectly!
Technologies: iOS8, SWIFT, XCode 6
Using swift, what is the best way to save an external website's html/css/js, modify that saved data with my own css / js, and then load it in the view. This way, the external page loads with my custom styles/js already implemented.
I'm not sure how complicated your use of the UIWebView is but, the quickest implementation I can think of (aside from the evaluateJS route you've already done):
Create a property to decide if a request has been hijacked yet (by you).
var hijacked = false
provide the UIWebViewDelegate protocol method.
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
//if true it must be our version, reset hijack and allow to load
if hijacked {
hijacked = false
return true
}
//original request. Don't let it load, instead trigger manual loader.
else {
hijacked = true
manuallyLoadPage(request)
return false
}
}
Then you just need a method to fetch the page, get the text, manipulate the text, and then load the page with your version.
func manuallyLoadPage(request: NSURLRequest) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
var html = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
html = html.stringByReplacingOccurrencesOfString("</head>", withString: "<script>alert(\"I added this\")</script></head>", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
self.webView.loadHTMLString(html, baseURL: response.URL!)
}
task.resume()
}
This is just a quick and dirty approach, you may want to do a more thorough job of tracking which requests are hijacked requests etc... This one just assumes an even odd kind of approach. You could obviously manipulate the html however you want, I just added the JS alert as a proof of concept.