31

I need to look at the HTML of a page given by a certain URL. If I have this, what is the most efficient and synchronous way to get the HTML source for that URL using Swift? I haven't been able to find a concise way online that returns it into a variable as opposed to printing it in a completionHandler.

I need to manipulate the source outside of whatever call uses the URL. How is this done in Swift?

Alex Beals
  • 1,965
  • 4
  • 18
  • 26
  • 4
    Friendly note to the readers: this questions asks specifically for a *synchronous* method. Don't use these answers if you don't specifically need it. The normal way is to use *asynchronous* methods. Thanks. – Eric Aya May 22 '18 at 12:47

5 Answers5

57

Disclaimer : Since this is getting quite a lot of views, I just want to remind everyone that this answer here is synchronous, and will block your app if you do it on the main thread. You should always do this asynchronously (in a background thread), but the question asked for a synchronous method, so it would be out of scope to explain how to do it here.


You should probably look at the method :

+ stringWithContentsOfURL:encoding:error (docs)

You would call it like this in Objective C :

NSString *myURLString = @"http://google.com";
NSURL *myURL = [NSURL URLWithString:myURLString];

NSError *error = nil;
NSString *myHTMLString = [NSString stringWithContentsOfURL:myURL encoding: NSUTF8StringEncoding error:&error];

if (error != nil)
{
    NSLog(@"Error : %@", error);
}
else
{
    NSLog(@"HTML : %@", myHTMLString);
}

So in Swift 3 and 4, the equivalent would be :

let myURLString = "https://google.com"
guard let myURL = URL(string: myURLString) else {
    print("Error: \(myURLString) doesn't seem to be a valid URL")
    return
}

do {
    let myHTMLString = try String(contentsOf: myURL, encoding: .ascii)
    print("HTML : \(myHTMLString)")
} catch let error {
    print("Error: \(error)")
}

You might want to adapt the encoding (see the constants) depending on which encoding your page's using.


Old answer, Swift 2.2 :

let myURLString = "http://google.com"
guard let myURL = NSURL(string: myURLString) else {
    print("Error: \(myURLString) doesn't seem to be a valid URL")
    return
}

do {
    let myHTMLString = try String(contentsOfURL: myURL)
    print("HTML : \(myHTMLString)")
} catch let error as NSError {
    print("Error: \(error)")
}

Old answer, Swift 1.2 :

let myURLString = "http://google.com"

if let myURL = NSURL(string: myURLString) {
    var error: NSError?
    let myHTMLString = NSString(contentsOfURL: myURL, encoding: NSUTF8StringEncoding, error: &error)

    if let error = error {
        println("Error : \(error)")
    } else {
        println("HTML : \(myHTMLString)")
    }
} else {
    println("Error: \(myURLString) doesn't seem to be a valid URL")
}
DCMaxxx
  • 2,534
  • 2
  • 25
  • 46
  • `contentsOfURL: myURL` throws an issue "value of optional type 'NSURL?' not unwrapped. Can't edit it, because of just one sign. – Jurik Feb 10 '15 at 14:04
8

Swift 3:

    if let url = URL(string: "https://www.google.com/trends/hottrends/atom/hourly") {
        do {
            let contents = try String(contentsOf: url)
            print(contents)
        } catch {
            // contents could not be loaded
        }
    } else {
        // the URL was bad!
    }
Crashalot
  • 33,605
  • 61
  • 269
  • 439
6

An updated @DCMaxx answer to Swift 2.2 :

let myURLString = "http://www.yahoo.com"

if let myURL = NSURL(string: myURLString) {
    var error: NSError?
    let myHTMLString = try! NSString(contentsOfURL: myURL, encoding: NSUTF8StringEncoding)

    if let error = error {
        print("Error : \(error)")
    } else {
        print("HTML : \(myHTMLString)")
    }
} else {
    print("Error: \(myURLString) doesn't  URL")
}
Meseery
  • 1,845
  • 2
  • 22
  • 19
  • 4
    You have declared an variable `error ` in your code it's never used at all, and the other very important thing is that you are disabling the error propagation using the `try!`, but it's not recommended, because you are telling the compiler that you never going to receive an error but if you receive it you will have a runtime error, test it with the address `"http://www.asdsadsadas,com"` and you will have a runtime error. – Victor Sigler Mar 11 '16 at 20:15
6

more compact functional example

let myURLString = "https://google.com"

let myHTMLString = try URL(string: myURLString)
    .flatMap { try Data(contentsOf: $0) }
    .flatMap { String(data: $0, encoding: .ascii) }
ha100
  • 1,563
  • 1
  • 21
  • 28
4

This is the way to go in Swift 2:

let myURLString = "https://duckduckgo.com/"

if let myURL = NSURL(string: myURLString) {

    do {
        let myHTMLString = try String(contentsOfURL: myURL, encoding: NSUTF8StringEncoding)
        print("HTML : \(myHTMLString)")
    } catch {
        print("Error : \(error)")
    }
} else {
    print("Error: \(myURLString) doesn't  URL")
}

Also as an extra related to previous answers:
Note that Swift 2 introduces a new error handling approach that produces much clearer code for programmers to read, it does away with complexities like & to pass in NSErrors, and it gives you greater safety by ensuring you catch all errors.

Only use try! if you are 100% sure that the call won't fail.

Further reading: https://www.hackingwithswift.com/new-syntax-swift-2-error-handling-try-catch

Javier Cadiz
  • 12,326
  • 11
  • 55
  • 76