Swift 2 - Cannot load UIWebView in separate ViewController - ios

I'm running into a bizarre problem - I cannot load a UIWebView in the background of a separate viewcontroller. Specifically, I have a UITableViewCell with a button to load a UIWebView in the viewcontroller behind it. However, I keep getting the error "found nil while unwrapping an Optional value"...
Here is my code:
func loadWebView(URLString:String) {
print("the urlstring is \(URLString)")
let theRequestURL = NSURL (string: URLString)
let theRequest = NSURLRequest (URL: theRequestURL!)
fullScreenWebView.loadRequest(theRequest)
}
The print statement yields the correct URL, so the issue isn't that the URL string isn't being passed.
Furthermore, if I load the UIWebView from the viewcontroller it's defined in, it works perfectly. I'm guessing the issue has to do with not being the correct viewcontroller when loadWebView() is called... I read Unable to display URL in WebView from another view controller, which was somewhat helpful.
What am I doing wrong?

When calling loadRequest using
SecondViewController().loadWebView("URL STRING HERE")
You are creating a new instance of the SecondViewController rather than using the one that has already been loaded.
What you need is call loadRequest on the instance that is already in memory, and has your UIWebView initialized.
For this you can get reference of the previous view controller and call a method to load your request
Assuming SecondViewController is the controller with UIWebView
Add this In the top view controller (Controller with your UITableView)
func loadWebView(URLString:String) {
print("the urlstring is \(URLString)")
let theRequestURL = NSURL (string: URLString)
if let theRequest = NSURLRequest (URL: theRequestURL!) {
//In case you push view controller on a navigation stack
let vc : SecondViewController = self.backViewController()
vc.loadWebView(theRequest)
}
}
//Get previous view controller on navigation stack
func backViewController() -> UIViewController? {
if let stack = self.navigationController?.viewControllers {
for(var i=stack.count-1;i>0;--i) {
if(stack[i] == self) {
return stack[i-1]
}
}
}
return nil
}
Now in your SecondViewController, add
func loadWebView(request : NSURLRequest) {
// Replace customWebView with the reference variable of your `UIWebView`
customWebView.loadRequest(request)
}

The reason you are getting this error is because you are force unwrapping a nil value. I would always suggest unwrapping nullable values using an if let statement:
func loadWebView(URLString:String) {
print("the urlstring is \(URLString)")
if let theRequestURL = NSURL (string: URLString) {
let theRequest = NSURLRequest (URL: theRequestURL!)
fullScreenWebView.loadRequest(theRequest)
}
}
Not every string can be converted into a URL, which is why NSURL(string:) returns a optional value. I would inspect why your URLString can't be parsed into a URL

Related

Xcode Opening a WebView in another ViewController

I'm trying to make an app with a button which launch a webview.
I've followed many tutorial and read differents topic about the subject but I cant get it working : I'm getting this message when I test my code :
"Cannot call value of non-function type UIWebView!"
Here's the steps I did until now
Adding a button in the principal view Controller
Creating an another view Controller named 'WebViewController'
Adding a segue to link the button to WebViewController
Creating a new Cocoa Touch Class file 'WebViewController'
Setting the WebViewController custom class with the WebViewController class
Adding a webView in the WebViewController ViewController named 'myWebView'
Here's the WebViewController class (in which I got the error when I run the project)
import UIKit
class WebViewController: UIViewController{
#IBOutlet weak var myWebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//define url
let url = NSURL (string: "http://www.my-url.com")
//request
let req = NSURLRequest(url: url as! URL)
//load request into the webview
myWebview(req as URLRequest) //error happens here :
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Here's a screenshot (picture talks more than long text, right =)
Thanks !
You can use SFSafariViewController:
import SafariServices
let url = URL(string: "https://www.google.com")
let safariVC: SFSafariViewController = SFSafariViewController(url: url)
self.present(safariVC, animated: true, completion: nil)
I used swift 3 syntax.
That code opens a Safari Web view and you dont need to create segues and view controlles in storyboard.
Try to use:
let url = NSURL (string: "https://google.com")
let request = NSURLRequest(url: url as! URL)
self. myWebView.loadRequest(request as URLRequest)
This code works for me

How to open url programmatically but not open in browser swift

I need open url programmatically when user click to cell, but not need segue to Safari browser. It is need for statistic on site.
This is need to imitation like post request
I do:
UIApplication.shared.openURL(URL(string: url)!)
But this open Safari browser.
If I'm not mistaking, you want to open the URL in the application it self (without navigating to external -Safari- browser). Well, if that's the case, you should use UIWebView:
You can use the UIWebView class to embed web content in your app. To
do so, create a UIWebView object, attach it to a window, and send it a
request to load web content. You can also use this class to move back
and forward in the history of webpages, and you can even set some web
content properties programmatically.
WebViewController:
I suggest to add a UIWebView into a ViewController (called WebViewController) and present the ViewController when needed; ViewController on storyboard should looks like:
DON'T forget to assign a Storyboard ID for it.
And the ViewController:
class WebViewController: UIViewController {
#IBOutlet weak private var webVIew: UIWebView!
var urlString: String?
override func viewDidLoad() {
if let unwrappedUrlString = urlString {
let urlRequest = URLRequest(url: URL(string: unwrappedUrlString)!)
webVIew.loadRequest(urlRequest)
}
}
#IBAction private func donePressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Usage:
Consider that you want present it when the user taps a button in another ViewController:
class ViewController: UIViewController {
#IBAction private func donePressed(_ sender: Any) {
let stoyrboard = UIStoryboard(name: "Main", bundle: nil)
// withIdentifier: the used storyboard ID:
let webViewController = stoyrboard.instantiateViewController(withIdentifier: "WebViewController") as! WebViewController
webViewController.urlString = "https://www.google.com/"
}
}
Or specifically for your case (selecting a cell):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// withIdentifier: the used storyboard ID:
let webViewController = stoyrboard.instantiateViewController(withIdentifier: "WebViewController") as! WebViewController
webViewController.urlString = "URL GOES HERE..."
}
Hope this helped.
if your question really means that you have some data on an external website that you need to access, so that you can extract a piece of information from it, then you probably don't need to display the rendered webpage at all.
You can extract the html content of a page like this
var text = ""
let url = URL(string: "https://www.bbc.co.uk")
do
{
text = try String(contentsOf: url!, encoding: String.Encoding.utf8)
}
catch
{
print("error \(error.localizedDescription)")
}
if !text.isEmpty
{
parseThisPageSomehow(text)
}
How you parse the text depends on what you need to get out of it, but this approach will give you the data you need.
If you use http rather than https, you will run into the well-documented transport security issue, which will force you to either set up a temporary override for http, or start using https
Transport security has blocked a cleartext HTTP

Pass url from UIWebView 1 in ViewController 1 to UIWebView 2 in ViewController 2

I have UIWebView in my first ViewController, and I want if the user clicks on any link inside UIWebView 1 should open new ViewController (ex. InfoViewController) and passes the URL to new UIWebView inside InfoViewController.
EDIT:
My Storyboard
My Code:
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL (string: "https://google.com");
let requestObj = NSURLRequest(URL: url!);
webview.delegate = self;
webview.loadRequest(requestObj);
}
func webview(WebViewNews: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if (navigationType == UIWebViewNavigationType.LinkClicked) {
//Push to new view controller here and pass new url:
let url = request.URL
let infoViewController = self.storyboard?.instantiateViewControllerWithIdentifier("InfoViewController") as! InfoViewController
infoViewController.passURL = url.absoluteString //Add passURL property in your InfoViewController
self.navigationController!.pushViewController(infoViewController, animated: true)
//prevent the current webview from loading the page
return false
}
//webview will load first time normally/other requests will load
return true
}
set the webview delegate to self:
webview.delegate = self
And then you can add this delegate method. This runs whenever it tries to load a url. You can add a if check to make sure it is a link they have clicked and run your code to change screen and prevent the current webview from loading the url by returning false:
//Update: got method name wrong:
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if (navigationType == UIWebViewNavigationType.LinkClicked {
//Push to new view controller here and pass new url:
let url = request.URL
let infoViewController = self.storyboard?.instantiateViewControllerWithIdentifier("InfoViewController") as InfoViewController
infoViewController.passURL = url.absoluteString //Add passURL property in your InfoViewController
self.navigationController!.pushViewController(infoViewController, animated: true)
//prevent the current webview from loading the page
return false
}
//webview will load first time normally/other requests will load
return true
}
You need to pull the link from your first UIWebview:
iPhone - UIWebview - Get the URL of the link clicked
Open the new ViewController and pass the string.
Direct your second UIWebview to open the URL.

Getting " unexpectedly found nil" error when setting "self imageView! image" in Swift

I am getting the above error in this collection view-detail view code.
Detail VC:
class DetailViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView?
var photo: NSDictionary?
var image1:UIImage = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//downloadFromUrl(url)
if let photo = photo {
let url1 = (photo["images"]!["thumbnail"]!!["url"])! as! String
let url:NSURL = NSURL(string: url1 as String)!
//print(url)
downloadFromUrl(url)
}
}
func downloadFromUrl(url: NSURL) {
//self.imageView.hnk_setImageFromURL(url)
let config:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session:NSURLSession = NSURLSession(configuration: config)
let request:NSURLRequest = NSURLRequest(URL: url)
let task = session.downloadTaskWithRequest(request) { (location, response, error) -> Void in
let data:NSData = NSData(contentsOfURL: location!)!
let image:UIImage = UIImage(data: data)!
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.image1 = image
print(self.image1)
self.imageView.image = self.image1
})
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And here is some methods in collection VC:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! PhotosViewCell
cell.backgroundColor = UIColor.brownColor()
cell.photo = self.photos[indexPath.row] as? NSDictionary
//self.p = self.photos[indexPath.row] as! NSDictionary
//print("Link is \(cell.photo!["link"]!)")
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let p = self.photos[indexPath.row] as! NSDictionary
let VC:DetailViewController = DetailViewController()
VC.photo = p
self.presentViewController(VC, animated: true, completion: nil)
}
I know I am probably getting this error because viewDidLoad() is not yet ready but what is the solution and I do need this dictionary inside viewDidLoad() because I need to call that downloadFromUrl() method.
Oh, I am not using prepareforsegue() method. tried that too, maybe I was doing wrong.
In the debug area, I am getting the following:
2016-01-06 13:30:55.075 AuthCheck[3623:74910] Warning: Attempt to present <AuthCheck.DetailViewController: 0x7ff103dfb6c0> on <AuthCheck.PhotosViewController: 0x7ff103c2fd90> whose view is not in the window hierarchy!
<UIImage: 0x7ff103cd1820>, {150, 150}
fatal error: unexpectedly found nil while unwrapping an Optional value
Which means correct image is being downloaded but it is not being able to set to imageView's image property. No idea why?
Will someone tell me the solution please?
You are getting a runtime error because you are force-unwrapping an optional that happens to contain no value (i.e., it is nil). Please check your optionals before unwrapping (it is good practice):
print(photo!) // <--Dangerous
if let photo = photo {
print(photo) // <--Safe
}
...unless your code path makes it 100% sure that the optional does contain a value.
You are downloading data from the internet; that should typically be an asynchronous operation, because it takes significant time and you don't want to freeze your main thread (user interface) while it completes.
So you need to perform it on the background, and use the obtained data inside a completion handler (i.e., block or closure).
ViewDidLoad() is called when the view controller is loaded, meaning that it happens when you create the instance, before you present the view controller
// viewDidLoad() is called right here
let VC:DetailViewController = DetailViewController()
// this happens after you try to print photo
VC.photo = p
This means that your optional variable photo is still null when you print it. To avoid the error, you could unwrap the variable with
if let photo = photo {
print(photo)
}
Although this wouldn't help you accomplish your goal. So, do your tasks in viewDidAppear rather than viewDidLoad, which is called after the view becomes visible.
override func viewDidAppear() {
super.viewDidAppear()
print(photo!)
downloadFromUrl(url)
}
Or, if you need to do the task before the view appears, create a separate function in DetailViewController and call it after assigning VC.photo.
func task() {
print(photo!)
downloadFromUrl(url)
}
then in CollectionView, do
let p = self.photos[indexPath.row] as! NSDictionary
let VC:DetailViewController = DetailViewController()
VC.photo = p
VC.task()
Actually a safer way to do it is to call downloadFromUrl(url) from the didSet method like this :
var photo: NSDictionary = NSDictionary() {
didSet {
// your code to get the url here you can test if photo is nil
downloadFromUrl(url)
}
}
But this is not the problem here. Since you don't use the storyboard to initialise your viewController (let VC = DetailViewController()) you never instantiate the content of your viewController (for exemple the imageView)

Pushing ViewController Results In Cant Unwrap Optional.None

I have an issue in Swift. When I press a cell on my table I do a segue into another ViewController which has a UIWebView and loads a webpage. This works fine. The custom ViewController code looks like this:
class ViewController_ShowView : UIViewController
{
var showName: String?
var showUrl: String?
#IBOutlet var webView: UIWebView
override func viewDidLoad()
{
super.viewDidLoad()
self.title = showName
var requestURL = NSURL(string: showUrl)
var request = NSURLRequest(URL: requestURL)
webView.loadRequest(request)
}
}
However, I am trying to load the ViewController from the AppDelegate (when a user clicks on a UILocalNotification) and it crashes on the web view.loadRequest(request) line from above. Here is how I am attempting to call it:
var currentShowsViewController = viewController.topViewController as ViewController_CurrentShows
var viewControllerToPush = ViewController_ShowView()
viewControllerToPush.showName = "Test"
viewControllerToPush.showUrl = "http://www.google.com"
currentShowsViewController.navigationController.pushViewController(viewControllerToPush, animated: false)
This causes a crash with the error Cant Unwrap Optional.None
I am confused why it works from the perform segue method but not this way...
Problem caused by this line of code:
var viewControllerToPush = ViewController_ShowView()
You should instantiate view controller from its storyboard. Here is how you can do it:
let storyboard : UIStoryboard = UIStoryboard(name: "MainStoryboard", bundle: nil);
let viewControllerToPush : ViewController_ShowView
= storyboard.instantiateViewControllerWithIdentifier("showViewVC") as ViewController_ShowView;
Note that you should also open your storyboard and set the view controller's identifier to showViewVC.
Remarks:
The exception get thrown because #IBOutlet var webView: UIWebView does not get initialized. #IBOutlets in Swift are optionals thus you receive Cant Unwrap Optional.None.

Resources