SWIFT - Loading local CSS file - ios

I am trying to add some local css to my webview but it seems that I have a problem with the css file in my app :
Nothing happens when I try to load the css from the local css file but when I replace '%#' by a online css file it works fine.
Thank you for your help.
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.delegate = self
let html: String! = "<html><head></head><body><div id=\"postcontent\"><h1>Hello</h1></div></body></html>"
webview.loadHTMLString(html, baseURL: nil)
}
func webViewDidFinishLoad(_ webView: UIWebView){
let path = Bundle.main.path(forResource: "styles", ofType: "css")
let javaScriptStr: NSString = "var link = document.createElement('link'); link.href = '%#'; link.rel = 'stylesheet'; document.head.appendChild(link)"
let javaScripthPath = NSString(format: javaScriptStr, path!)
webview.stringByEvaluatingJavaScript(from: javaScripthPath as String)
print(javaScripthPath)
}
}

Your HTML file is loaded from a string, so it's loaded into a web view that can't access files on the file system (probably due to the Same Origin Policy that browsers implement). It can only access resources similarly injected in via loadHTMLString.
If you want to use local CSS, load your HTML from a file instead of a string. This will give the web view access to the CSS if it's in the same directory.
Here is a demo, first, the HTML is now a file.
Then, your code can look like this:
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.delegate = self
let path = Bundle.main.path(forResource: "app", ofType: "html")!
let url = URL(fileURLWithPath: path)
let request = URLRequest(url: url)
webview.loadRequest(request)
}
func webViewDidFinishLoad(_ webView: UIWebView){
let path = Bundle.main.path(forResource: "styles", ofType: "css")!
let javaScriptStr = "var link = document.createElement('link'); link.href = '\(path)'; link.rel = 'stylesheet'; document.head.appendChild(link)"
webview.stringByEvaluatingJavaScript(from: javaScriptStr)
}
}

Related

Creating a custom webview from scratch

I am in a situation where I want more flexibility for webview than the offered by UIWebview and not at all by WKWebview. Things like customizing web request before start eg sending headers and redirections. Also as we know UIWebview officially deprecated now in iOS 12.
I'm looking forward to a generic webview now, I know its possible as open source examples say Firefox for iOS.
If you've been using any of your projects or know please tell me. Or if you can give me some information how can this be achieved or any tutorails links would be helpful.
Declare the WebView object
var webView: WKWebView!
//Load the data in webView
func loadUrl() {
var webView : WKWebView!
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let userContentController: WKUserContentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
userContentController.addUserScript(script)
webView = WKWebView(frame: CGRect.zero, configuration: configuration)
webView.uiDelegate = self
webView.navigationDelegate = self
webView.allowsLinkPreview = true
//Load the Data from URL
let url = URL(string: "Your URL")
var urlRequest = URLRequest(url: url! as URL)
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.setValue("user-agent", forHTTPHeaderField: "User-Agent")
webView.load(urlRequest)
//Load the Data from local HTML
do {
guard let filePath = Bundle.main.path(forResource: "Your-HTML-File", ofType: "html")
else {
print ("FILE READING ERROR")
return
}
//get the content of HTML File
let contents = try String(contentsOfFile: filePath , encoding: .utf8)
webView.loadHTMLString(contents, baseURL:URL(fileURLWithPath: Bundle.main.bundlePath))
}
catch {
print ("FILE HTML ERROR")
}
}
//Delegate Method that called when webView finish Loading.
//You can apply css or style here
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
///Local CSS File
guard let path = Bundle.main.path(forResource: "Local-CSS-File", ofType: "css") else { return }
let script = "var head = document.getElementsByTagName('head')[0];var link = document.createElement('link');link.rel = 'stylesheet';link.type = 'text/css';link.href = '\(path)';link.media = 'all';head.appendChild(link);"
webView.evaluateJavaScript(script) {(result, error) in
if let error = error {
print(error)
}
}
//External CSS File
let externalCSSLink = "your-css-file-url"
let script2 = "var head = document.getElementsByTagName('head')[0];var link = document.createElement('link');link.rel = 'stylesheet';link.type = 'text/css';link.href = '\(externalCSSLink)';link.media = 'all';head.appendChild(link);"
webView.evaluateJavaScript(jsString1) {(result, error) in
if let error = error {
print(error)
}
}
}

Why does using Nsurl request keeps telling me to rename to "init(url)"

Also trying to add the ability to detect a video and download it to the application in short a web-based app used for download any kind of video and has the ability to store it within the app
import UIKit
class ViewController: UIViewController {
#IBOutlet var Webview: UIWebView!
#IBOutlet var SearchBar: UISearchBar!
override func viewDidLoad() {
let url = NSURL(string: "https://www.google.com")
let request = NSURLRequest(URL: url! as URL) Webview.loadRequest(request)
SearchBar.text = "http://"
}
func searchBarSearchButtonClicked(searchbar: UISearchBar) {
searchbar.resignFirstResponder()
let text = SearchBar.text
let url = NSURL(string: text!)
let urlRequest:URLRequest = URLRequest(url: url! as URL)
// let request = NSURLRequest(URL: url! as URL)
Webview.loadRequest(urlRequest)
}
}
You need to use like this in swift
if let url = URL(string: "https://www.google.com"){
let requestObj = URLRequest(url: url)
Webview.loadRequest(requestObj)
}
URL :
URL is a swift struct, so is passed by value.
NSURL :
NSURL is an Objective-C class. Is inherits from NSObject
In general, prefer the new struct versions of things unless you need to subclass for some reason.
An object representing the location of a resource that bridges to URL; use NSURL when you need reference semantics or other Foundation-specific behavior.
Both URL and NSURL is accepted in swift. but you have used swift then most refer URL MORE.
Use URL and URLRequest instead NSURL and NSURLRequest in Swift.
let url = URL.init(string: "https://www.google.com")
let request = URLRequest.init(url: url!)
Webview.loadRequest(request)
For Reference you can refer : Reference

let url = URL(string: item)! returning nil in swift

Really cannot figure this one out, the URL prints and is not equal to nil, and it works in the browser when I paste it in. Any ideas?
import UIKit
class WebViewController: UIViewController {
var postLink: String = String()
#IBOutlet weak var mywebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
print(postLink)
let attempt = postLink
let url: URL = URL(string: attempt)!
let request: URLRequest = URLRequest(url: url)
mywebView.loadRequest(request)
}
The error occurs at:
let url: URL = URL(string: attempt)!
I am guess you are passing the urlString from another controller, do that instead
var postUrlString:String? //<-- make it optional
override func viewDidLoad() {
super.viewDidLoad()
guard let urlString = postUrlString, // forced unwrapped
let url = URL(string: urlString)
else { return } // if there is any optional we return
// else continue
let request: URLRequest = URLRequest(url: url)
mywebView.loadRequest(request)
}
The error is simple, postLink, you are providing to create URL is not correct. My guess is its empty.(Just a guess) and you have forgot to set it.
Avoid using force unwrapping ! in your code as much as possible.
You should either use guard let or if let in the scenarios.
In your case you might want to show some error to user when you are unable to load. Instead of
let url: URL = URL(string: attempt)!
use
if let url = URL(string: attempt) {
let request = URLRequest(url: url)
mywebView.loadRequest(request)
} else {
// Do something like. Show an alert that could not load webpage etc.
}
Alternatively you can use guard let, but it would require to return from the function where it is used. To know more about uses of if and guard let you can go through by blog post here.

I want to invoke Webview.loadRequest() multiple times in Swift3

I am new to IOS development. And I am working to get page load time of web on IOS emulators.
Since, I am targeting top 100 alexa websites, it would be difficult to load each url and record time for load. So, I am planning to automate it.
Below is my code:
import UIKit
class ViewController: UIViewController, UIWebViewDelegate{
var startTime = Date()
#IBOutlet weak var WebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// let URL = Foundation.URL(string: "http://www.youtube.com")
startTime = Date()
print(startTime)
for url in returnURLArray(){
WebView.delegate = self
WebView.loadRequest(URLRequest(url: URL(string: url)!))
print("I am trying to load...."+url)
sleep(100)
print("back to execute other urls")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webViewDidFinishLoad(_ WebView: UIWebView)
{
//Check here if still webview is loding the content
if (WebView.isLoading){
print("I am loading now...")
return;
}
//after code when webview finishes
let finishTime = Date()
print(finishTime.timeIntervalSince(startTime))
NSLog("Webview loding finished")
}
func returnURLArray() -> [String] {
let file = "topalexawebsites.txt" //this is the file. we will write to and read from it
var text2 = String()
var urlArr = [String]()
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
do{
print(path)
text2 = try String(contentsOf: path, encoding: String.Encoding.utf8).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
urlArr = text2.components(separatedBy: "\n")
// print(text2)
// for urls in urlArr{
// print(urls)
//}
}
catch let err as NSError{
print(err)
}
}
return urlArr
}
}
I want to execute all urls one by one in my topalexawebsites.txt and record load time for it.
But, here I am seeing only one cycle is working, and since urls are in a loop, only the also one gets loaded.

WKWebView does load resources from local document folder

In my Swift iOS app, I want to download some dynamic HTML pages from a remote server, save them in the document directory, and display those pages from document directory.
I was using this to load the page:
var appWebView:WKWebView?
...
appWebView!.loadRequest(NSURLRequest(URL: NSURL(fileURLWithPath: htmlPath)))
Everything works on the simulator, but when I moved to real phones, it just showed a blank page. I then connected to the app using Safari, and found it complained with "Failed to load resource".
I then tried to first read the content of the page at htmlPath, then use
appWebView!.loadHTMLString()
to load the page. It works when the HTML page is simple. But if the HTML references something else, i.e. a JavaScript file also in the document directory (with an absolute path like <script src="file:////var/mobile/Containers/Data/Application/762035C9-2BF2-4CDD-B5B1-574A0E2B0728/Documents/xxxxx.js">), it will fail to load.
Does anyone know why this happens, and how to resolve the issue?
More info:
XCode version: 7.3.1
Deployment Target: 8.1 (I tried to use 9.3 too, but that didn't help.)
This is a simplified version of what I have used to load local files in a project of mine (iOS 10, Swift 3). I have just updated my code (7.5.2017) after testing it out again on iOS 10.3.1 and iPhone 7+ as requested by Raghuram and Fox5150 in the comments.
I just created a completely new project and this is the folder structure:
Update 19.04.2018: Added a new feature to download a .zip with HTML, CSS, JS files, unzip it in /Documents/ (Alamofire + Zip) and then load those files into the webView. You can find it in the GitHub sample project as well. Again, feel free to fork & star! :)
Update 08.02.2018: finally added a GitHub sample project, which also includes a local JavaScript file. Feel free to fork & star! :)
Version 1 with webView.loadFileURL()
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
let htmlUrl = URL(fileURLWithPath: htmlPath!, isDirectory: false)
webView.loadFileURL(htmlUrl, allowingReadAccessTo: htmlUrl)
webView.navigationDelegate = self
view = webView
}
}
Version 2 with webView.loadHTMLString()
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView()
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
let folderPath = Bundle.main.bundlePath
let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
do {
let htmlString = try NSString(contentsOfFile: htmlPath!, encoding: String.Encoding.utf8.rawValue)
webView.loadHTMLString(htmlString as String, baseURL: baseUrl)
} catch {
// catch error
}
webView.navigationDelegate = self
view = webView
}
}
Gotchas to look out for:
Make sure that your local html/js/css files are in Project -> Target -> Build Phases -> Copy Bundle Resources
Make sure that your html files don't reference relative paths e.g. css/styles.css because iOS will flatten your file structure and styles.css will be on the same level as index.html so write <link rel="stylesheet" type="text/css" href="styles.css"> instead
Given the 2 versions and the gotchas here are my html/css files from the project:
web/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Offline WebKit</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<h1 id="webkit-h1">Offline WebKit!</h1>
</body>
</html>
web/css/styles.css
#webkit-h1 {
font-size: 80px;
color: lightblue;
}
If somebody wants a GitHub sample project, tell me in the comments section and I'll upload it.
Swift 4 Method
This method allows WKWebView to properly read your hierarchy of directories and sub-directories for linked CSS/JS files. You do NOT need to change your HTML, CSS or JS code.
Updated for Xcode 9.3
Step 1
Import the folder of local web files anywhere into your project. Make sure that you:
☑️ Copy items if needed
☑️ Create folder references (not "Create groups")
☑️ Add to targets
Step 2
Go to the View Controller with the WKWebView and add the following code to the viewDidLoad method:
let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "website")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
index – the name of the file to load (without the .html extension)
website – the name of your web folder (index.html should be at the root of this directory)
Conclusion
The overall code should look something like this:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.uiDelegate = self
webView.navigationDelegate = self
let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "Website")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
}
}
If any of you have further questions about this method or the code, I'll do my best to answer. :)
This solution helped me:
[configuration.preferences setValue:#YES forKey:#"allowFileAccessFromFileURLs"];
This works well (Swift 3, Xcode 8):
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
if let url = Bundle.main.url(forResource: "file", withExtension: "txt")
{
do
{
let contents = try String(contentsOfFile: url.path)
webView.loadHTMLString(contents, baseURL: url.deletingLastPathComponent())
}
catch
{
print("Could not load the HTML string.")
}
}
}
}
This works nicely with file URL or remote URL, and whether file is in the bundle or in documents:
if url.isFileURL {
webView.loadFileURL(url, allowingReadAccessTo: url)
} else {
let request = URLRequest(url: url)
webView.load(request)
}
Constructing the URLs this way allowed me to load resources from the document directory with WKWebView:
guard let docDir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else {
return
}
let resourceURL = docDir.appendingPathComponent("/Path/To/Your/Resource")
self.wkWebView.loadFileURL(resourceURL, allowingReadAccessTo: docDir)
The files must be in the document directory.
I implemented the following to retrieve a document:
let documentDirUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileNameWithExtension = "IMG_0002.PNG"
let indexFileUrl = documentDirUrl.appendingPathComponent(fileNameWithExtension)
if FileManager.default.fileExists(atPath: indexFileUrl.path) {
webView.loadFileURL(indexFileUrl, allowingReadAccessTo: documentDirUrl)
}
Check that ticket: iOS: How to load local files (not in the bundle) in a WKWebView?
var nsurl = URL(fileURLWithPath: URL(fileURLWithPath: URL(fileURLWithPath: documentsDirectory()).appendingPathComponent(user_appli).absoluteString).appendingPathComponent("index.html").absoluteString) //locally
var readAccessToURL: URL? = nsurl.deletingLastPathComponent?.deletingLastPathComponent
if let anURL = readAccessToURL {
webView?.loadFileURL(nsurl, allowingReadAccessTo: anURL)
}

Resources