I have wrote some code to load all html from url and parse it to remove header. So I got the rest of the html under header to show.
However, after clicking event in the body html, the screen shows full html from the URL.
Is there any solution for this? or Did I make a mistake to approach this problem?
The code I made is below
import UIKit
import Fuzi
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let myURLString = "http://yahoo.com"
var myHTMLString = ""
guard let myURL = URL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
myHTMLString = try String(contentsOf: myURL, encoding: .utf8)
} catch let error {
print("Error: \(error)")
}
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try! HTMLDocument(string: myHTMLString, encoding: String.Encoding.utf8)
let fullHtml:String = (doc.firstChild(xpath: "//*")?.rawXML)!
if let header = doc.firstChild(xpath: "//body/div/header") {
let headerString:String = header.rawXML
let withoutHeader = fullHtml.replacingOccurrences(of: headerString, with: "")
webView.loadHTMLString(withoutHeader as String, baseURL: nil)
}
} catch let error{
print(error)
}
}
Try this, it works with UIWebViewNavigationType.linkClicked. You can modify it to use with other UIWebViewNavigationType.
import UIKit
import Fuzi
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
let myURLString = "http://yahoo.com"
var myHTMLString = ""
guard let myURL = URL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
myHTMLString = try String(contentsOf: myURL, encoding: .utf8)
} catch let error {
print("Error: \(error)")
}
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try! HTMLDocument(string: myHTMLString, encoding: String.Encoding.utf8)
let fullHtml:String = (doc.firstChild(xpath: "//*")?.rawXML)!
if let header = doc.firstChild(xpath: "//body/div/header") {
let headerString:String = header.rawXML
let withoutHeader = fullHtml.replacingOccurrences(of: headerString, with: "")
webView.loadHTMLString(withoutHeader as String, baseURL: nil)
}
} catch let error{
print(error)
}
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .linkClicked
{
let myURLString = webView.request?.url.absoluteString
webView.stopLoading()
var myHTMLString = ""
let myURL = URL(string: myURLString)
do {
myHTMLString = try String(contentsOf: myURL, encoding: .utf8)
} catch let error {
print("Error: \(error)")
}
do {
// if encoding is omitted, it defaults to NSUTF8StringEncoding
let doc = try! HTMLDocument(string: myHTMLString, encoding: String.Encoding.utf8)
let fullHtml:String = (doc.firstChild(xpath: "//*")?.rawXML)!
if let header = doc.firstChild(xpath: "//body/div/header") {
let headerString:String = header.rawXML
let withoutHeader = fullHtml.replacingOccurrences(of: headerString, with: "")
webView.loadHTMLString(withoutHeader as String, baseURL: nil)
}
} catch let error{
print(error)
}
}
return true
}
I found an answer!
Thanks to #Sherman for the hint to solve this problem.
I had to replace a line of code below
if navigationType == .linkClicked
to
if navigationType == UIWebViewNavigationType.linkClicked
Then It works!
Related
I make some code using swift 4 to load image from URL, but every time I add images to server, it took a lot of time to load it in colection view or table view. I want to try store it in NScache but i dont understand to do it. can anyone help me, I'm new in swift :(
#objc func loadPosts() {
let url = URL(string: "http://someURL/Url.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "phomepost=\(homepost)"
request.httpBody = body.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
self.comments.removeAll(keepingCapacity: false)
self.images.removeAll(keepingCapacity: false)
self.collectionView?.reloadData()
guard let parseJSON = json else {
print("Error While Parsing")
return
}
guard let posts = parseJSON["posts"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
self.comments = posts.reversed()
for i in 0 ..< self.comments.count {
let path = self.comments[i]["path"] as? String
if !path!.isEmpty {
let url = NSURL(string: path!)!
let imageData = try? Data(contentsOf: url as URL)
let image = UIImage(data: imageData! as Data)!
self.images.append(image)
} else {
let image = UIImage()
self.images.append(image)
}
}
self.collectionView?.reloadData()
//print(posts.count)
} catch {
print(error)
}
}else{
print(error)
}
})
}.resume()
}
You can use something like this:
private let cache = NSCache<NSString, NSData>()
.....
func downloadImage(url: String, handler: #escaping(Data?, Error?) -> Void){
let cacheID = NSString(string: url)
if let cachedData = cache.object(forKey: cacheID) {
handler((cachedData as Data), nil)
}else{
if let url = URL(string: url) {
let session = URLSession(configuration: urlSessionConfig)
var request = URLRequest(url: url)
request.cachePolicy = .returnCacheDataElseLoad
request.httpMethod = "get"
session.dataTask(with: request) { (data, response, error) in
if let _data = data {
self.cache.setObject(_data as NSData, forKey: cacheID)
handler(_data, nil)
}else{
handler(nil, error)
}
}.resume()
} else {
// NetworkError is a custom error
handler(nil, NetworkError.invalidURL)
}
}
}
}
This will add a small animation while loading using image set.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromUrl(urlString: String) {
let loader1 = UIImage(named: "loaderImage1.png")
let loader2 = UIImage(named: "loaderImage2.png")
let loader3 = UIImage(named: "loaderImage3.png")
let imageArray = [loader1, loader2, loader3]
let animatedImage = UIImage.animatedImage(with: imageArray as! [UIImage], duration: 1.7)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{
self.image = imageFromCache
return
} else {
self.image = animatedImage
Alamofire.request(urlString, method: .get).response { (responseData) in
if let data = responseData.data {
DispatchQueue.main.async {
if let imageToCache = UIImage(data: data){
imageCache.setObject(imageToCache, forKey: urlString as AnyObject)
self.image = imageToCache
}
}
}
} //alamofire
}
}
}
I got a source code from a github page written in swift and implementing GoogleMaps. I now want to refactor the codes to use Alamofire and SwiftyJSON so that I can improve the code but I got confused because through my learning of swift I used Alamofire and swiftyJSON for every networking process so I am confused currently. the code below
typealias PlacesCompletion = ([GooglePlace]) -> Void
typealias PhotoCompletion = (UIImage?) -> Void
class GoogleDataProvider {
private var photoCache: [String: UIImage] = [:]
private var placesTask: URLSessionDataTask?
private var session: URLSession {
return URLSession.shared
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
func fetchPlacesNearCoordinate(_ coordinate: CLLocationCoordinate2D, radius: Double, types:[String], completion: #escaping PlacesCompletion) -> Void {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(radius)&rankby=prominence&sensor=true&key=\(appDelegate.APP_ID)"
let typesString = types.count > 0 ? types.joined(separator: "|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? urlString
guard let url = URL(string: urlString) else {
completion([])
return
}
if let task = placesTask, task.taskIdentifier > 0 && task.state == .running {
task.cancel()
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
placesTask = session.dataTask(with: url) { data, response, error in
var placesArray: [GooglePlace] = []
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(placesArray)
}
}
guard let data = data,
let json = try? JSON(data: data, options: .mutableContainers),
let results = json["results"].arrayObject as? [[String: Any]] else {
return
}
results.forEach {
let place = GooglePlace(dictionary: $0, acceptedTypes: types)
placesArray.append(place)
if let reference = place.photoReference {
self.fetchPhotoFromReference(reference) { image in
place.photo = image
}
}
}
}
placesTask?.resume()
}
func fetchPhotoFromReference(_ reference: String, completion: #escaping PhotoCompletion) -> Void {
if let photo = photoCache[reference] {
completion(photo)
} else {
let urlString = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=200&photoreference=\(reference)&key=\(appDelegate.APP_ID)"
guard let url = URL(string: urlString) else {
completion(nil)
return
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
session.downloadTask(with: url) { url, response, error in
var downloadedPhoto: UIImage? = nil
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(downloadedPhoto)
}
}
guard let url = url else {
return
}
guard let imageData = try? Data(contentsOf: url) else {
return
}
downloadedPhoto = UIImage(data: imageData)
self.photoCache[reference] = downloadedPhoto
}
.resume()
}
}
}
any help to refactor the codes to use Alamofire and swiftyJSON would be appreciated.
Both Alamofire and SwiftyJSON have pretty decent instructions, and there are plenty of examples online to look for. However, this would be a decent starting point - you need to replace your session.dataTask and session.downloadTask with Alamofire methods. For example, instead of:
session.downloadTask(with: url) { url, response, error in
var downloadedPhoto: UIImage? = nil
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(downloadedPhoto)
}
}
guard let url = url else {
return
}
guard let imageData = try? Data(contentsOf: url) else {
return
}
downloadedPhoto = UIImage(data: imageData)
self.photoCache[reference] = downloadedPhoto
}
.resume()
use this skeleton and implement your models and logic:
Alamofire
.request(url)
.responseJSON { dataResponse in
switch dataResponse.result {
case .success:
guard let json = JSON(dataResponse.data) else {
return
}
// Continue parsing
case .failure(let error):
// Handle error
print("\(error)")
}
}
I'm trying to parse some websites with Swiftsoup, let's say one of the websites is from Medium. How can I extract the body of the website and load the body to another UIViewController like what Instapaper does?
Here is the code I use to extract the title:
import SwiftSoup
class WebViewController: UIViewController, UIWebViewDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://medium.com/#timjwise/stop-lying-to-yourself-when-you-snub-panhandlers-its-not-for-their-own-good-199d0aa7a513")
let request = URLRequest(url: url!)
webView.loadRequest(request)
guard let myURL = url else {
print("Error: \(String(describing: url)) doesn't seem to be a valid URL")
return
}
let html = try! String(contentsOf: myURL, encoding: .utf8)
do {
let doc: Document = try SwiftSoup.parseBodyFragment(html)
let headerTitle = try doc.title()
print("Header title: \(headerTitle)")
} catch Exception.Error(let type, let message) {
print("Message: \(message)")
} catch {
print("error")
}
}
}
But I got no luck to extract the body of the website or any other websites, any way to get it work? CSS or JavaScript (I know nothing about CSS or Javascript)?
Use function body https://github.com/scinfu/SwiftSoup#parsing-a-body-fragment
Try this:
let html = try! String(contentsOf: myURL, encoding: .utf8)
do {
let doc: Document = try SwiftSoup.parseBodyFragment(html)
let headerTitle = try doc.title()
// my body
let body = doc.body()
// elements to remove, in this case images
let undesiredElements: Elements? = try body?.select("img[src]")
//remove
undesiredElements?.remove()
print("Header title: \(headerTitle)")
} catch Exception.Error(let type, let message) {
print("Message: \(message)")
} catch {
print("error")
}
I think something changed within Swift that disabled me from previewing my files. It worked fine previously. If I click on say a PDF file in my app, I see the title of the PDF, but the content of PDF (preview) area does not show.
Below is my code & logs & also the screenshot. If anyone has an idea of where I can fix the issue, any help would be greatly appreciated.
// When file is clicked this method is called
#objc private func handleTapped() {
guard let url = self.file.fileUrl else { return }
if self.file.isDownloaded {
self.showDocumentController(url: self.file.urlInDocumentsDirectory! as NSURL)
return
}
SVProgressHUD.showProgress(0)
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("pig.png")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(url, to: destination)
.downloadProgress { (download) in
DispatchQueue.main.async() {
SVProgressHUD.showProgress(Float(download.fractionCompleted))
}
}.validate(statusCode: 200..<300)
.response { (response) in
SVProgressHUD.dismiss()
guard response.response?.statusCode == 200 else { return }
let directoryURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let pathURL = URL(fileURLWithPath: directoryURL, isDirectory: true)
//pathURL: file:///var/mobile/Containers/Data/Application/6DDCCC30-107C-4613-B63D-18962C3D06D3/Documents/
guard let fileName = response.response?.suggestedFilename else { return }
//fileName: 05_기조강연_RobertMankin_BETTER+OFFICES+GREATER+INNOVATION.pdf
let fileURL = pathURL.appendingPathComponent(fileName)
//fileURL: file:///var/mobile/Containers/Data/Application/6DDCCC30-107C-4613-B63D-18962C3D06D3/Documents/05_%E1%84%80%E1%85%B5%E1%84%8C%E1%85%A9%E1%84%80%E1%85%A1%E1%86%BC%E1%84%8B%E1%85%A7%E1%86%AB_RobertMankin_BETTER+OFFICES+GREATER+INNOVATION.pdf
self.saveFileURL(url: fileURL as NSURL)
self.showDocumentController(url: fileURL as NSURL)
}
}
private func saveFileURL(url: NSURL) {
self.file.urlInDocumentsDirectory = url as URL
let realm = RealmService.defaultRealm
try! realm?.write {
realm?.add(self.file, update: true)
}
self.file = self.file.copyFromRealm()
}
private func showDocumentController(url: NSURL) {
let docController = UIDocumentInteractionController(url: url as URL)
docController.delegate = self
docController.presentPreview(animated: true)
}
// MARK: UIDocumentInteractionControllerDelegate methods
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
if let controller = UIApplication.shared.keyWindow?.topMostViewController() {
return controller
}
return UIViewController()
}
this is how the preview shows
Here Is The Code
import UIKit
import Alamofire
class ViewController: UIViewController, UIWebViewDelegate
{
#IBOutlet weak var WebView: UIWebView!
var NewsURL: String = ""
override func viewDidLoad()
{
super.viewDidLoad()
Self.LoadPdf()
}
func LoadPdf()
{
let url = NSURL (string: "\(http://)") //Your Pdf URL Here
let requestObj = NSURLRequest(URL: url!);
WebView.loadRequest(requestObj)
}
}
I am working on show image from url async. I have tried to create a new thread for download image and then refresh on main thread.
func asyncLoadImg(product:Product,imageView:UIImageView){
let downloadQueue = dispatch_queue_create("com.myApp.processdownload", nil)
dispatch_async(downloadQueue){
let data = NSData(contentsOfURL: NSURL(string: product.productImage)!)
var image:UIImage?
if data != nil{
image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue()){
imageView.image = image
}
}
}
When I was trying to debug that, when it comes to dispatch_async(downloadQueue), it jumps out the func. Any suggestion? Thx
**Swift 5.0+ updated Code :
extension UIImageView {
func imageFromServerURL(_ URLString: String, placeHolder: UIImage?) {
self.image = nil
//If imageurl's imagename has space then this line going to work for this
let imageServerUrl = URLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
if let url = URL(string: imageServerUrl) {
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//print("RESPONSE FROM API: \(response)")
if error != nil {
print("ERROR LOADING IMAGES FROM URL: \(error)")
DispatchQueue.main.async {
self.image = placeHolder
}
return
}
DispatchQueue.main.async {
if let data = data {
if let downloadedImage = UIImage(data: data) {
self.image = downloadedImage
}
}
}
}).resume()
}
}
}
Now wherever you required just do this to load image from server url :
Using swift 5.0 + updated code using placeholder image :
UIImageView.imageFromServerURL(URLString:"here server url",placeHolder: placeholder image in uiimage format)
Simple !
Use extension in Swift3. To resolve Network problem i recommend you use NSCache:
import UIKit
let imageCache = NSCache<NSString, AnyObject>()
extension UIImageView {
func loadImageUsingCache(withUrl urlString : String) {
let url = URL(string: urlString)
self.image = nil
// check cached image
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
self.image = cachedImage
return
}
// if not, download image from url
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
if let image = UIImage(data: data!) {
imageCache.setObject(image, forKey: urlString as NSString)
self.image = image
}
}
}).resume()
}
}
Hope it help!
Carrying on from Shobhakar Tiwari's answer, I think its often helpful in these cases to have a default image in case of error, and for loading purposes, so I've updated it to include an optional default image:
Swift 3
extension UIImageView {
public func imageFromServerURL(urlString: String, defaultImage : String?) {
if let di = defaultImage {
self.image = UIImage(named: di)
}
URLSession.shared.dataTask(with: NSURL(string: urlString)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error ?? "error")
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
})
}).resume()
}
}
This solution make scrolling really fast without unnecessary image updates.
You have to add the url property to our cell class:
class OfferItemCell: UITableViewCell {
#IBOutlet weak var itemImageView: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
var imageUrl: String?
}
And add extension:
import Foundation
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
let imageDownloadUtil: ImageDownloadUtil = ImageDownloadUtil()
extension OfferItemCell {
func loadImageUsingCacheWithUrl(urlString: String ) {
self.itemImageView.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.itemImageView.image = cachedImage
return
}
DispatchQueue.global(qos: .background).async {
imageDownloadUtil.getImage(url: urlString, completion: {
image in
DispatchQueue.main.async {
if self.imageUrl == urlString{
imageCache.setObject(image, forKey: urlString as AnyObject)
self.itemImageView.image = image
}
}
})
}
}
}
You can also improve it and extract some code to a more general cell class i.e. CustomCellWithImage to make it more reusable.
Here this code might help you.
let cacheKey = indexPath.row
if(self.imageCache?.objectForKey(cacheKey) != nil){
cell.img.image = self.imageCache?.objectForKey(cacheKey) as? UIImage
}else{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
if let url = NSURL(string: imgUrl) {
if let data = NSData(contentsOfURL: url) {
let image: UIImage = UIImage(data: data)!
self.imageCache?.setObject(image, forKey: cacheKey)
dispatch_async(dispatch_get_main_queue(), {
cell.img.image = image
})
}
}
})
}
With this image will download and cache without lagging the table view scroll
The most common way in SWIFT 4 to load async images without blink or changing images effect is use to custom UIImageView class like this one:
//MARK: - 'asyncImagesCashArray' is a global varible cashed UIImage
var asyncImagesCashArray = NSCache<NSString, UIImage>()
class AyncImageView: UIImageView {
//MARK: - Variables
private var currentURL: NSString?
//MARK: - Public Methods
func loadAsyncFrom(url: String, placeholder: UIImage?) {
let imageURL = url as NSString
if let cashedImage = asyncImagesCashArray.object(forKey: imageURL) {
image = cashedImage
return
}
image = placeholder
currentURL = imageURL
guard let requestURL = URL(string: url) else { image = placeholder; return }
URLSession.shared.dataTask(with: requestURL) { (data, response, error) in
DispatchQueue.main.async { [weak self] in
if error == nil {
if let imageData = data {
if self?.currentURL == imageURL {
if let imageToPresent = UIImage(data: imageData) {
asyncImagesCashArray.setObject(imageToPresent, forKey: imageURL)
self?.image = imageToPresent
} else {
self?.image = placeholder
}
}
} else {
self?.image = placeholder
}
} else {
self?.image = placeholder
}
}
}.resume()
}
}
example of use this class in UITableViewCell bellow:
class CatCell: UITableViewCell {
//MARK: - Outlets
#IBOutlet weak var catImageView: AyncImageView!
//MARK: - Variables
var urlString: String? {
didSet {
if let url = urlString {
catImageView.loadAsyncFrom(url: url, placeholder: nil)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
One of the best way is to used SDWebImage.
Swift Example:
import SDWebImage
imageView.sd_setImage(with: URL(string: "ImageUrl"), placeholderImage: UIImage(named: "placeholder.png"))
Objective C Example:
#import <SDWebImage/UIImageView+WebCache.h>
[imageView sd_setImageWithURL:[NSURL URLWithString:#"ImageUrl"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];