Does WKWebView require a URL? - swift

I am pulling in HTML content via an API that loads JSON and would like to use WKWebView in order to display it. Is it possible to use WKWebView without a URL?
This controller is built programmatically, so the webView is added as Subview. In the long run I will have the webView display below other content that is also pulled from the API.
This is the code I have:
var webView = WKWebView()
webView = WKWebView(frame: scrollView.bounds, configuration: WKWebViewConfiguration())
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.addSubview(webView)
webView.navigationDelegate = self as? WKNavigationDelegate
webView.loadHTMLString("<html><body>\(self.articleBody)</body></html>", baseURL: nil)
I get this error when I load my app:
Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service
In testing with a random URL and no HTML it works just fine.
I've looked at this question, but it doesn't have an answer that makes sense for me: com.apple.WebKit.WebContent drops 113 error: Could not find specified service

I'm still getting the same error, but I am at least seeing my content now ...
The issue had to do with where I was calling the webView.loadHTMLString. I had to move it inside of my URLSession code instead of keeping it outside.
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let devo = try decoder.decode(Devotion.self, from: data)
DispatchQueue.main.async {
self.articleBody = devo.body
self.webView.loadHTMLString("<html><body>\(self.articleBody)</body></html>", baseURL: nil)
}
} catch let err {
print("Err", err)
}
}
}.resume()

Related

macos swift helper app not working when launched automatically

I'm making an app that's meant to automatically log into school internet, and I'm trying to make it so that you save the credentials in the main app, and the helper app gets registered to launch at login, which works.
However, when the helper app opens from logging in, it doesn't send a web request to log into the internet, but when I open the helper app manually, it does. Keep in mind that it's meant to send a request whenever the internet status is changed to "connected"
(it's also meant to send the request every time you're connected to internet, but it only fires once)
import Cocoa
import Network
#main
class AppDelegate: NSObject, NSApplicationDelegate {
let defaults = UserDefaults.init(suiteName: "replaced userdefaults group name thing")
func applicationDidFinishLaunching(_ aNotification: Notification) {
let usernamestored = defaults!.string(forKey: "username")!
let passwordstored = String(decoding: kread(service: "detnsw-autologin", account: usernamestored)!, as: UTF8.self) // kread() refers to a function i have in another file for reading values from keychain
let url = URL(string:"the login page url")
guard let requestUrl = url else { fatalError() }
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
let poststring = "csrfmiddlewaretoken=&username=\(usernamestored)&password=\(passwordstored)"
request.httpBody = poststring.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Error took place \(error)")
return
}
if let data = data, let _ = String(data: data, encoding: .utf8) {
//print("Response data string:\n \(dataString)")
//print(response.statusCode)
}
}
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
task.resume() // this is supposed to fire every time the app is connected to the internet
}
}
let queue = DispatchQueue(label: "Monitor")
monitor.start(queue: queue)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
I've fixed the issue by putting all of the code that loads the username and password into the function that runs when the internet is connected, so the final code looks something like this, instead of the code being outside of the function so that it only loads once.
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
let usernamestored = defaults!.string(forKey: "username")!
...
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in {...}
task.resume()
}
}

How to save multi images from URLs to local storage in swift?

I am developing Restaurant app.
There are 340 foods data.
I am getting this data from backend that developed with Laravel.
App is working well in online.
But, although network is turned off, All foods data should be displayed in app.
So, I tried to save foods data to local.
It is good to save text data to local(exactly, UserDefaults and FileSystem).
But, when I try to save images to local from urls, It occur error and don't save to local exactly.
Have you ever seen such problems?
If yes, I appreciate your help.
Thanks
I don't know exactly what error you faced, but I think the answer to the link can help you.
How do I make JSON data persistent for offline use (Swift 4)
Additionally, image data would be good to be cached.
If you communicate based on URLSession, you can process caching as below.
Saving an Image to a Cache
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
Bring up cached images
let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString)
Code
class Cache {
static let imageCache = NSCache<NSString, UIImage>()
}
extension UIImageView {
func imageDownload(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
if let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async() { [weak self] in
self?.contentMode = mode
self?.image = cacheImage
}
}
else {
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
print("Download image fail : \(url)")
return
}
DispatchQueue.main.async() { [weak self] in
print("Download image success \(url)")
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.contentMode = mode
self?.image = image
}
}.resume()
}
}
}
To make caching more convenient, use a library called Kingfisher.
Kingfisher
You need to manage lazy loadings, you can use SDWebImages instead of manage it manually
SDWebImages will automatically manage your images caches and will also load them without internet here is a simple usage of it
add pod in podfile
pod 'SDWebImage'
usagae:
import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))

Strange error nw_protocol_get_quic_image_block_invoke dlopen libquic failed

I'm new to swift and iOS in general, please keep that in mind.
I get this error when opening the CFReadStream. It does not matter if I open the read or write streams, the app always fails.
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let host: CFString = NSString(string: hostIP)
let port: UInt32 = UInt32(self.VNCport)
self.password = password
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readStream, &writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
if readStream == nil {
print("Erro read")
}
if writeStream == nil {
print("Erro write")
}
inputStream!.delegate = self
outputStream!.delegate = self
inputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
outputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
inputStream!.open()
outputStream!.open()
I made a clean project with just this function and a Button, the result is the same. No quic lib is used in the project.
Can someone help?
I faced the same error in a different context, in XCode 12.0.1 too. It might not be related, but I suspect its an issue with accessing the run loop of the main thread. I was just trying out some introductory code I found online, and faced the same issue, so this is a bug, rather than a problem with your code. Here's how you can get a piece of code that has the same issue:
git clone git#github.com:BestKora/CS193P-Fall-2017-Demo-iOS12.git
cd "CS193P-Fall-2017-Demo-iOS12/Cassini L10"
xed . # this opens XCode (CLI tool stands for XCode editor)
Having said that, by rewriting the code, I was able to prevent this issue. Maybe you can find something amongst the code below to fix your specific issue:
Specifically, instead of using the following (DispatchQueue.global)
private func fetchImage() {
if let url = imageURL {
spinner.startAnimating()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let urlContents = try? Data(contentsOf: url)
DispatchQueue.main.async {
if let imageData = urlContents, url == self?.imageURL {
self?.image = UIImage(data: imageData)
}
}
}
}
}
I use URLSession.shared.dataTask, and this error no longer happens:
private func fetchImage() {
print("fetching image")
if let url = imageURL {
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else {
return
}
// maybe try dispatch to main
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
}
task.resume()
}
}
I had same issue in an Widget Target, but solved it by adding "public" to the variables.
Fixed struct is shown below.
struct LastCommitEntry: TimelineEntry {
public let date: Date
public let commit: Commit
}
This is just a Simulator message. It won't appear on a real device, and it doesn't affect the behavior of your app, so ignore it.
I was getting this issue in url session.
Thank you
Restarting my simulator did the trick.
For me, it started working when I encoded the data using String(data: safeData, encoding: .utf8) line:
if let safeData = data {
let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)
}
In my case it was a problem with the model. I was working with Codable Model but it wasn't parsing the data properly. When I used the simulator the error was there but when I used my device it disappeared, neverthelesss, the collection view wasn't showing. When I change my model the error was corrected.
You'll need to adopt that specific delegate you're using in your class.
Like in below example:
class ViewController: UIViewController, UITextFieldDelegate
And don't forget to set your current class as the delegate, like below (for my own case):
IBOutlet weak var searchTextField: UITextField!
searchTextField.delegate = self

Load page into webview swift 2 from java class

I am developing an App for the iPhone using Xwebview which enables me to download a page then interact with the javascript on the downloaded page.
All works, but if the internet connection drops, a default local page is loaded, informing the user there is no internet connection. The page displays a retry button that, when pressed checks, the internet connection: if the connection is made the app tries to connect again to the external page and load the page into the webview.
I cannot get this to work: the code downloads the page (I can see this in my session data) but I can't get that page to load back into the webview.
override func viewDidLoad() {
super.viewDidLoad()
login()
}
func login()
{
// *********** Get stored hashkey **************
let hashcode = getHashcode()
// ********** Check network connection *********
let netConnection = Connection.isConnectedToNetwork()
print("net connection: ", netConnection)
if netConnection == true
{
if hashcode != "00000"
{
print("local key found", hashcode)
// We dont have local key
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
//webview.loadRequest(NSURLRequest(URL: NSURL(string: "about:blank")!))
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let url:NSURL = NSURL(string: serverLocation + onlineLoginApi)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let paramString = "/?username=username&password=password"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.downloadTaskWithRequest(request) {
(
let location, let response, let error) in
guard let _:NSURL = location, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let urlContents = try! NSString(contentsOfURL: location!, encoding: NSUTF8StringEncoding)
guard let _:NSString = urlContents else {
print("error")
return
}
print(urlContents)
}
task.resume()
// you must tell webview to load response
webview.loadRequest(request)
}
else{
print("local key found", hashcode)
// ********* Found local key go to site pass key over ************
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let req = NSMutableURLRequest(URL: NSURL(string:serverLocation + onlineLoginApi + "?hashcode=\(hashcode)")!)
req.HTTPMethod = "POST"
req.HTTPBody = "/?hashcode=\(hashcode)".dataUsingEncoding(NSUTF8StringEncoding)
NSURLSession.sharedSession().dataTaskWithRequest(req)
{ data, response, error in
if error != nil
{
//Your HTTP request failed.
print(error!.localizedDescription)
} else {
//Your HTTP request succeeded
print(String(data: data!, encoding: NSUTF8StringEncoding))
}
}.resume()
webview.loadRequest(req)
}
}
else{
// No connection to internet
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let root = NSBundle.mainBundle().resourceURL!
let url = root.URLByAppendingPathComponent("/www/error-no-connection.html")
webview.loadFileURL(url, allowingReadAccessToURL: root)
print("No internet connection")
}
}
class jsapi: NSObject {
// Reconnect button on interface
func retryConnection()
{
print("Reconnect clicked")
dispatch_async(dispatch_get_main_queue())
{
let netConnections = Connection.isConnectedToNetwork()
if netConnections == true {
let netalert = UIAlertView(title: "Internet on line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
let url = self.serverLocation + self.onlineLoginApi
let hashcode = ViewController().getHashcode()
if(hashcode != "00000") {
let url = url + "?hashcode=\(hashcode)"
print("url: ", url)
}
ViewController().loadPagelive(url)
}
else{
let netalert = UIAlertView(title: "Internet off line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
}
}
print("retryConnect end")
}
}
You try to perform the loadPagelive(url) on a new instance of your ViewController, not on the current one shown on the screen, that's why you don't see any update.
You should create a delegate or a completion block in order to execute code on you ViewController instance loaded on the screen: every time you do ViewController(), a new object is created.
You can try using the delegate pattern, which is simple to achieve. I will try to focus on the important part and create something that can be used with your existing code:
class ViewController: UIViewController {
let jsapi = jsapi() // You can use only 1 instance
override func viewDidLoad() {
super.viewDidLoad()
// Set your ViewController as a delegate, so the jsapi can update it
jsapi.viewController = self
login()
}
func loadPagelive(_ url: URL) {
// Load page, probably you already have it
}
}
class jsapi: NSObject {
weak var viewController: ViewController?
func retryConnection() {
// We check if the delegate is set, otherwise it won't work
guard viewController = viewController else {
print("Error: delegate not available")
}
[... your code ...]
// We call the original (and hopefully unique) instance of ViewController
viewController.loadPagelive(url)
}
}

swift NSURL gets back an "unable to read data" message even with https://

I am trying to learn iOS following a course and they ask to do the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//var string1 = "http://www.google.com"
//var string1 = "https://www.weather-forecast.com/locations/San-Antonio/forecasts/latest"
//var url = NSURL(string: string1)
var url = NSURL(string: "https://google.com")
print(url)
if url != nil {
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
var urlError = false
if error == nil {
var urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(urlContent)
} else {
urlError = true
}
if urlError == true {
self.showError()
}
})
task.resume()
} else {
showError()
}
}
the app doesn't show any web page content and when debugging I find that the object for the url says that it is "unable to read data"
I have tried with http and https. I have tried with different web sites.
I have tried the address in the safari of the simulator and it loads.
Can someone tell me why is this not working
Thanks in advance.
gariva
You're using wrong encoding. The webpage you're trying to fetch (http://www.google.com/) uses ISO-8859-1.
I was able to reproduce your issue. Fetch worked when I changed encoding. Try this:
var urlContent = NSString(data: data!, encoding: NSISOLatin1StringEncoding)
For display web page you should use UIWebView element. Something like this:
let url = NSURL(string: "https://google.com")
let webView = UIWebView(frame: self.view.frame)
self.view.addSubview(webView)
webView.loadRequest(NSURLRequest(URL: url!))