Macos WKWebView only gets partial content - swift

I am trying to use a Macos WKWebView to get the text content of a Facebook page. However, the WKWebView's load method only gets a small part of the Facebook page: I need to scroll down manually several dozen times in order to get more and more content. How can I automatize the process to load the full page?
func getPageContent(url: URL) {
let urlRequest = URLRequest (url:url)
myWebView.load(urlRequest)
myWebView.evaluateJavaScript("document.body.innerHTML;") { (result,error) in
if error != nil {
print(error!.localizedDescription)
} else {
// grab text
rawHtml = result as! String
// rawHtml contains only a small part of the page!
}
}

Related

How to Load Several Swift WKWebviews and Know When They Are All Done

I am using WKWebView to render several (around 100) web pages that I then need to render to PDF. I am using the createPDF method of WKWebView to accomplish this. The reason I'm doing each individual page in its own web view is because createPDF doesn't respect page breaks in the HTML (as far as I know).
So I have a class where I start the loop to render each page:
class PrintVC: ViewController, WKNavigationDelegate {
var pages = [Page]()
func start(){
//A "page" is a struct that has the string content to load each web view
for page in pages{
let webView = WKWebView()
webView.navigationDelegate = self
webView.loadHTMLString(page.content, baseURL: Bundle.main.bundleURL)
}
}
}
I know the page is ready to be saved to PDF in the didFinish navigation delegate method:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let config = WKPDFConfiguration()
config.rect = CGRect(x: 0, y: 0, width: 792, height: 612)
//Create the PDF
webView.createPDF(configuration: config){ result in
switch result{
case .success(let data):
do{
try data.write(to: URL(fileURLWithPath: "file-???.pdf"))
}catch let error{
print(error)
}
case .failure(let error):
print(error)
}
}
}
}
The trouble I'm having is I don't know when each individual page is done rendering. I also don't know how to pass each page's name to be used in the file path to save it.
How can I start a bunch of WKWebView loads and know when they are all done? Or better still, how can I reuse the same WKWebView and load each individual page in the same way? I assume using the same web view would be a better use of memory.
How can I start a bunch of WKWebView loads and know when they are all done?
Well, you'd need to identify which web view caused the delegate method to be called. It is for this reason that the first parameter - webView: WKWebView - exists.
One way is to put each (web view, pair) into a dictionary ([WKWebView: Page]). Then start the loading:
// assume you have declared a property "self.webViewDict"
for page in pages{
let webView = WKWebView()
webView.navigationDelegate = self
self.webViewDict[webView] = page
webView.loadHTMLString(page.content, baseURL: Bundle.main.bundleURL)
}
When one finishes loading, you can identify the page by doing webViewDict[webView]. You should then remove the web view from the dictionary:
webViewDict[webView] = nil
if webViewDict.isEmpty {
// everything is loaded!
}
how can I reuse the same WKWebView and load each individual page in the same way?
Note that if you use the same WKWebView, you'll have to load the pages sequentially. The same web view can't load multiple things at the same time.
You can just removed the loaded pages from pages. If you don't want to do that, you can copy pages to another var first.
In start, load the first page:
if let firstPage = pages.first {
webView.loadHTMLString(firstPage.content, baseURL: Bundle.main.bundleURL)
}
When you successfully load a page, do the same thing again:
case .success(let data):
pages.removeFirst()
if let firstPage = pages.first {
webView.loadHTMLString(firstPage.content, baseURL: Bundle.main.bundleURL)
} else {
// we are done!
}

How to pass a local file URL in WKWebView so it treats it as a dropped file

I am trying to upload a local file with drag and drop and WKWebView. I have only the URL of the file. The MacOS app provides an extension to Final Cut Pro. Therefore what I can actually drop in the WKWebView is an XML from FCP. I obtain the URL of the file from there. In the web project, loaded in the WKWebView, there is already implementation of upload, which I am trying to use.
What is tried and achieved so far:
A custom subclass of WKWebView loads the web project
An XML file is dragged and dropped from FCP in the webview of the extension.
I parse the XML string and gather the URL of the file location from the XML
I pass the URL to the WKWebView and the web view plays the video from the url.
Is it possible to pass the URL in such a way that the WKWebView treats it as a dropped file and how? This is the current implementation of performDragOperation in the WKWebView subclass:
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
guard let pasteboardObjects = sender.draggingPasteboard.readObjects(forClasses: [NSString.self, NSURL.self], options: nil), pasteboardObjects.count > 0 else {
return false
}
pasteboardObjects.forEach { (object) in
if let XMLString = object as? NSString {
NSLog("It's a string: %#: ", XMLString)
//Acquire URL here …
let urlString = "file:///Users/someuser/SomeLocation/SomeVideo.MP4"
let urlData = urlString.data(using: .utf8)
sender.draggingPasteboard.clearContents()
sender.draggingPasteboard.setData(urlData, forType: .fileURL)
//uncomment the next line and comment the previous 2 and the same result will be achieved
//self.loadFileURL(url, allowingReadAccessTo: url)
}
if let url = object as? NSURL {
NSLog("It's a URL %#", url.absoluteString!)
}
}
return super.performDragOperation(sender)
}

WKWebKit does not refresh webpage

I am using Xcode 8.3.3 and Swift 3 to develop an app for the iMac using Cocoa. My goal is to use VCgoToWebPage and display a webpage to the user. My program calls this function many times, but the only webpage I see is the last one called. How do I implement a window refresh inside this function and wait for the webpage to be fully rendered?
func VCgoToWebPage(theWebPage : String) {
let url = URL(string: theWebPage)!
let request = URLRequest(url: url)
webView.load(request)
/*The modal box allows the web pages to be seen. Without it, after a series of calls to VCgoToWebPage only the last page called is displayed. The modal box code is just for debugging and will be removed. */
let alert = NSAlert()
alert.messageText="calling EDGAR page"
alert.informativeText=theWebPage
alert.addButton(withTitle: "OK")
alert.runModal()
}
You can use navigation delegate to make sure navigation to a page is complete before trying to load another. Have your class conform to WKNavigationDelegate and set webView.navigationDelegate to that class instance.
var allRequests = [URLRequest]()
func VCgoToWebPage(theWebPage : String) {
guard let url = URL(string: theWebPage) else {
return
}
let request = URLRequest(url: url)
if webView.isLoading{
allRequests.append(request)
} else {
webView.load(request)
}
}
func webView(WKWebView, didFinish: WKNavigation!){
if let nextRequest = allRequests.first{
webView.load(nextRequest)
allRequests.removeFirst()
}
}

set tabbar item to custom image like instagram

I have been trying to look for this solution for a very long time, but I can not find any documentation or tutorials on it. I want to set a tabbar item to the user's profile picture. Each user can only log into the app through Facebook. The app then takes the user's profile picture from facebook and stores it in firebase storage. I want to retrieve that image and set it to a tabbar item, but unfortunately, it is not working. Here is the code I have so far:
override func awakeFromNib() {
super.awakeFromNib()
self.tabBarItem.title = "MY ACCOUNT"
if self.loggedInUserUid != nil {
let imageRef = FIRStorage.storage().reference().child((loggedInUserUid)!+"/profile_pic.jpg")
imageRef.data(withMaxSize: 75 * 144 * 96, completion: { (data, error) -> Void in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.tabBarItem.image = image
})
}).resume()
}}
i get the error:
Could not load the "" image referenced from a nib in the bundle with identifier
In the storyboard click on the image. Go to the column on the right (activate it if it's not visible by clicking on the appropriate button) and find a place that says something like "identifier". Enter a short name there. Then use this identifier instead of the path you're using.
Based on the error message it looks like you've defined the image in your storyboard without an identifier. If that's true you don't need all the code to load the image manually.
You should pick one method or the other. But not both.
I'd love to be more specific but I'm away just now and on my phone.

Correct asynchronous Authentication while keeping a responsive UI

What it's supposed to be
I have a username field. When the username is entered and the sendButton is clicked, the userdata is fetched with a asynchronousRequest as a JSON file.
After the sendButton is clicked, I want to display an ActivityIndicator.
The UI shall still be responsive, while the request is made.
How it is now
I click the sendButton and the UI freezes. Even the ActivityIndicator does NOT get displayed.
The code
LoginVC:
func buttonTouchedUpInside(sender: UIButton) {
toggleActivityIndicatorVisibilityOn(true)
LoginManager.sharedInstance.checkUserForCredentials(username: textFieldLogin.text, password: "")
toggleActivityIndicatorVisibilityOn(false)
}
func loginManagerDidFinishAuthenticationForUser(userData: [String:String]?){
// Delegate Method, which works with the fetched userData.
}
LoginManager
func checkUserForCredentials(#username: String ,password: String) -> Void {
let url = NSURL(string: "\(Config.checkCredentialsUrl)username=\(username)")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: .mainQueue()) { (response, data, error) -> Void in
if error != nil {
//Display error-message
}
var error : NSError?
let json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as? [String:String]
self.delegate?.loginManagerDidFinishAuthenticationForUser(json)
}
}
In short: I want the request to be made in background, that the Activityindicator is shown and the UI stays responsive. After the asynchronous request successfully fetched the json, the delegate method shall be called
The second line of code in the buttonTouchedUpInside method, which reads LoginManager.sharedInstance.checkUserForCredentials(username: textFieldLogin.text, password: "") is calling an asynchronous function within it, which means that it is not blocking the next line of code... which is the one that (I am guessing) triggers your loading screen to become invisible again.
Basically, your loading screen is showing up, but it is immediately being hidden again. To fix at least the part with your loading screen, put the third line of code in the buttonTouchedUpInside function in the callback method loginManagerDidFinishAuthenticationForUser instead.