I've got a URL and I'm trying to get the HTML content of the site the following way:
func getHtml(_ urlString: String) -> String? {
guard let url = URL(string: urlString) else {
return nil
}
do {
let html = try String(contentsOf: url, encoding: .ascii)
return html
} catch let error {
print("Error: \(error)")
return nil
}
}
if let html = getHtml("https://m.youtube.com/") {
print(html)
}
My issue is, that this gets me the html of the desktop version of the site, however I need the html of the mobile version.
I'm not looking for a workaround for this specific site, but for a general solution, so that, given any URL of a mobile site, it doesn't default to getting me the html of the desktop site.
If you use Viewcontroller in iOS to get the HTML, you can use hidden WKWebView as an alternative and implement the WKNavigationDelegate which has the didFinish method where you can use webView.evaluateJavaScript. As wkwebview is loading from mobile, you will get the mobile version html. Here is the sample of the code.
import UIKit
import WebKit
class YourViewController: UIViewController, WKNavigationDelegate {
let webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func BtnClicked(_ sender: Any) {
loadWeb(url: "https://m.youtube.com/")
}
func loadWeb(url: String) {
if let myURL = URL(string: url) {
let request = URLRequest(url: myURL)
webView.navigationDelegate = self
webView.load(request)
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.outerHTML") { (data, error) in
//use html data
print("data", data, error.debugDescription)
}
}
}
Related
I am building an application in Swift to find a USTA Ranking. I am using SwiftSoup to scrape a web page.
This is my code:
import UIKit
import SwiftSoup
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let myURL = URL(string:"https://www.usta.com/en/home/play/player-search/profile.html#?uaid=2010002111/")!
let html = try! String(contentsOf: myURL, encoding: .utf8)
do {
let doc: Document = try SwiftSoup.parseBodyFragment(html)
// my body
let body = doc.body()
var rating = try body?.getElementsByClass("cell__text")[8]
print(rating!)
print(body)
} catch Exception.Error(_, let message) {
print("Message: \(message)")
} catch {
print("error")
}
}
}
When this runs, it gives me an error, saying that it is out of range. After looking through the output, I figured out that anything that has "ng-cloak" in it, makes the remaining stuff inside of it disappear. How can I prevent this from happening, so that I can get the ranking I want?
Thanks.
How to get a string of text like this:
Simple Text Example...
from macOS' Safari page via Swift 4?
#IBOutlet weak var label: NSTextField!
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let string = "http://127.0.0.1:8080/101"
let url = NSURL(string: string)
let request = URLRequest(url: url! as URL)
self.webView.load(request as URLRequest)
//this doesn't work
label.stringValue = webView.webFrame.frameElement.innerText
}
Here is HTML structure:
<html>
<head></head>
<body>
Simple Text Example...
</body>
</html>
UPDATE:
I've tried the following method but it has Void return...
webView.evaluateJavaScript("document.body.innerText") { (result, error) in
if error != nil {
print(result)
}
}
for iOS, in the webview's delegate method
func webViewDidFinishLoad(_ webView: UIWebView) {
var string = webView.stringByEvaluatingJavaScript(from: "document.body.textContent")
print(string)
}
for macOS, in the webview's navigation delegate:
override func viewDidLoad() {
webview.nagivationDelegate = self
...
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.body.textContent") { (string, error) in
print(string)
}
}
You have to check outerhtml and innerhtml, textContent and many more tag like that:
if let htmlOuter = self.webView.stringByEvaluatingJavaScript(from: "document.body.outerHTML"){
}
Or
if let htmlInner = self.webView.stringByEvaluatingJavaScript(from: "document.body.innerHTML"){
}
Or
if let content = self.webView.stringByEvaluatingJavaScript(from: "document.body. textContent"){
}
I am making an application which makes a lot of requests from an API. So I don't want to copy and past the code over and over. I was wondering how I can reuse my code in a some more efficient way? Maybe with extensions?
This is my code know:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "****"
let password = "****"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let urlProjects = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: urlProjects! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
self.projectNaam.append(description)
}
if let id = item["Id"] as? String {
self.projectId.append(id)
}
if let companyId = item["CompanyId"] as? String {
self.companyId.append(companyId)
}
}
}
self.apiRequestCompani()
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
Yes, you can use Extensions to create a BaseViewController and extend that where you want to use your code over and over again. Then you should abstract all dynamic data over input parameters to that method.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getApiRequest (Parameters) {
//API Request
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Call method in baseviewcontroller
getApiRequest(parameters)
//Call method in self
self.getApiRequest(parameters)
}
override func getApiRequest(Parameters) {
//IF you need to override default configuration
}
So I don't want to copy and past the code over and over.
Absolutely right, no one aiming to get duplicated code; That's the issue of massive view controller. This issue appears since the view controller layer in your application handles most of the responsibilities, such as: getting data from the network, how data should be represented, deliver the formatted data to the view layer, etc...
There are many approaches for solving such an issue (using an appropriate architectural pattern for your application), for simplicity, I would recommend to apply the MVC-N (or MVCNetworking) approach into your app, it is almost the same usual MVC, with a separated files (managers), represent a new layer for handling -for instance- the integration with the external APIs.
Applying the MVN-N should not be that complex, nevertheless it needs to be described well (which might be too abroad to be descried in the answer), I would suggest to check the above mentioned apple example, also watching this video should be useful.
I want to show the JSON data grabbed from a server on a Table View. The problem is, I can't get it to show up on it. I have tried several different methods and searched a lot to find a solution, but I can't.
My code (all of it) is shown below and I hope somebody can help me out. Thanks in advance.
import UIKit
import Alamofire
import SwiftyJSON
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getJSON()
}
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
}
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Table View Stuff
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableTitle.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
// cell config
cell.title!.text = tableTitle[indexPath.row]
cell.body!.text = tableBody[indexPath.row]
return cell
}
}
The Alamofire network request is asynchronous, meaning you can't know when the result will come back.
The problem here is that you reload the tableView outside the scope of the Alamofire request, so it is executed before the data comes back.
The reload should happen in the same scope, and on the main thread, for example:
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
I think #Eric said almost everything in his answer, nevertheless, not it's a good decision in design keep the code for make the network request in your same UITableViewController this keep a couple between two things that are independents and change for differents reasons.
My advice is separate the two parts of the code decoupling the dependency between your two layers. In this way when you need to change anything related with your networking request handler you don't need to change it in any place where you make the same request, it's an advice!!!.
In case you want to do it, you can use closures to hanlde the async behaviour of Alamofire passgin the completionHandlerinside the wrapper you make to handle the networking requests, for example, let's define a simple wrapper using the singleton pattern (it's just for the purpose of explain the sample, you can handle it as you want).
import AlamofireImage
import SwiftyJSON
class NetworkHandler {
/// The shared instance to define the singleton.
static let sharedInstance = RequestManager()
/**
Private initializer to create the singleton instance.
*/
private init() { }
func getJSON(completionHandler: (json: JSON?, error: NSError?) -> Void) {
Alamofire.request(.GET, http://announcement.vassy.net/api/AnnouncementAPI/Get/).responseJSON { response in
switch(response.result) {
case .Success(let value):
let json = JSON(value)
completionHandler(json: json, error: nil)
case .Failure(let error):
completionHandler(json: nil, error: error)
}
}
}
}
Then in your UITableViewController you can call the new wrapper to Alamofire in this way:
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NetworkHandler.sharedInstance.getJSON { [weak self] (json, error) -> Void in
// request was made successful
if error == nil {
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
// rest of your code
}
In the above way you keep the code decoupled, it's a good design.
I hope this help you
I can't for the life of me find out how to simply open a Pinterest board (by launching the APP if it's installed) through an anchor within HTML.
I've taken a look at: https://developers.pinterest.com, but still can't find what i'm looking for. It seems most of the documentation out there is geared more towards the action of pinning an item, rather than viewing.
All I want to do is open a users profile. For example, i've dug up the following which works great for alternative social media links:
Facebook
Twitter
Instagram
While I'm able to launch the Pinterest app, I'm unsure what the parameters would be passed to open a specific profile / board on Pinterest:
Pinterest
Thanks to: http://handleopenurl.com/ for help with the above, but it seems Pinterest is absent. Any clues?
Looks like Pinterest has published an iOS SDK, and defined some URLS:
Pin
pinterest://pin/285063851385287883/
Board
pinterest://board/meaghanror/cats-cats-cats/
User
pinterest://user/carlrice/
See more at https://developers.pinterest.com/ios/
Working Code for Swift 4.x
func openSocialMedia(appURI : String, webURI : String){
let appURL = NSURL(string: appURI)!
let webURL = NSURL(string: webURI)!
if UIApplication.shared.canOpenURL(appURL as URL) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(appURL as URL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(appURL as URL)
}
} else {
//redirect to safari because the user doesn't have Instagram
if #available(iOS 10.0, *) {
UIApplication.shared.open(webURL as URL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(webURL as URL)
}
}
}
How to call
#IBAction func didPressInstagram(_ sender: Any) {
let appHandle = "instagram://user?username=erashukr"
let webHandle = "https://instagram.com/erashukr"
self.openSocialMedia(appURI: appHandle, webURI: webHandle)
}
#IBAction func didPressGplus(_ sender: UIButton) {
let appHandle = "gplus://plus.google.com/u/0/+Ashutoshkumarrr"
let webHandle = "https://plus.google.com/u/0/+Ashutoshkumarrr"
self.openSocialMedia(appURI: appHandle, webURI: webHandle)
}
#IBAction func didPressFacebook(_ sender: Any) {
let appHandle = "fb://profile?id=erashukr"
let webHandle = "https://facebook.com/erashukr"
self.openSocialMedia(appURI: appHandle, webURI: webHandle)
}
#IBAction func didPressTwitter(_ sender: UIButton) {
let appHandle = "twitter://user?screen_name=ace_ashu"
let webHandle = "https://twitter.com/ace_ashu"
self.openSocialMedia(appURI: appHandle, webURI: webHandle)
}
#IBAction func didPressPinterest(_ sender: UIButton) {
let appHandle = "pinterest://user/apple"
let webHandle = "https://www.pinterest.com/apple/"
self.openSocialMedia(appURI: appHandle, webURI: webHandle)
}
100% Working