Reloading a WebView with a new url from another ViewController - ios

I'm having trouble reloading a UIWebiView from another view controller. When I try to send the new url and reload it, the app crashes due to the webview being nil.
I made a simple test app to narrow down my problem, right now its a simple page with a webivew showing google and a button on the view. When the button is pressed, it modally presents another page. This page only has one button that when pressed, it dismissed itself and calls a function in the first view asking to refresh it with a new url. Heres my code:
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlRequest = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
webView.loadRequest(urlRequest)
}
#IBAction func showViewPressed(sender: AnyObject) {
let showView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("showView")
self.presentViewController(showView, animated: true, completion: nil)
}
func reloadWebView(newURL: String) {
let urlRequest = NSURLRequest(URL: NSURL(string: newURL)!)
webView.loadRequest(urlRequest)
}
}
and for the second view:
class ReloadViewController: UIViewController {
#IBAction func buttonPressed(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: {
ViewController().reloadWebView("https://www.yahoo.com")
})
}
}
it crashes with fatal error: unexpectedly found nil while unwrapping an Optional value

Alright, so it seems before I ask a question I spend hours trying to find a solution to no avail. Then 5 minutes after I post a question thinking i'll never find the solution... I find it. So I found a solution that was for a tableview here this also happened to apply to webviews. So for anyone in the future looking for the same thing, heres what I did:
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let urlRequest = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
webView.loadRequest(urlRequest)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(reloadWebView), name:"reload", object: nil)
}
#IBAction func showViewPressed(sender: AnyObject) {
let showView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("showView")
self.presentViewController(showView, animated: true, completion: nil)
}
func reloadWebView() {
let urlRequest = NSURLRequest(URL: NSURL(string: "https://yahoo.com")!)
webView.loadRequest(urlRequest)
}
}
and for the second view:
class ReloadViewController: UIViewController {
#IBAction func buttonPressed(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: {
NSNotificationCenter.defaultCenter().postNotificationName("reload", object: nil)
})
}
}
and it worked beautifully!

Related

Why are the results of PDFKit .beginFindStrings nil?

Self-teaching novice here.
My end goal:
iOS/Mac app that loads a directory of PDFs, searches each for an array of strings, and lists which PDFs contain which strings where.
Problem in prototyping for only one PDF:
I receive a perplexing nil from loading a chosen PDF, running .beginFindStrings(["and", "the"], withOptions: .caseInsensitive) and waiting for the Notification .PDFDocumentDidEndFind to check [PDFSelection] .
That shouldn't be. Memory shows the PDF is loaded. Am I doing something wrong with threads? I think I've followed the async advice here: PDFKit background search
Code
import UIKit
import MobileCoreServices
import PDFKit
class ViewController: UIViewController, UIDocumentPickerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
var matchesFound: PDFSelection?
#IBOutlet weak var resultsLabel: UILabel!
#IBAction func importPDF(_ sender: Any) {
let picker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
picker.delegate = self
picker.allowsMultipleSelection = false
self.present(picker, animated: true)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard urls.count == 1 else {return}
let data = try! Data(contentsOf: urls[0])
let subjectPDF = PDFDocument.init(data: data)
guard subjectPDF!.isLocked == false else {return}
subjectPDF!.beginFindStrings(["the", "and"], withOptions: .caseInsensitive)
NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)
}
#objc func onDidFindMatch(_ notification: Notification) {
resultsLabel.text = "\(String(describing: matchesFound?.string))"
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
}
Code with Markup
import UIKit
import MobileCoreServices
import PDFKit
class ViewController: UIViewController, UIDocumentPickerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
//Array of PDFSelection search results
var matchesFound: PDFSelection?
//Temporary display for search result strings
#IBOutlet weak var resultsLabel: UILabel!
//Choose a PDF to import, temporarily limited to one
#IBAction func importPDF(_ sender: Any) {
let picker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
picker.delegate = self
picker.allowsMultipleSelection = false
self.present(picker, animated: true)
}
//Load the picked PDF as subjectPDF, if unlocked
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard urls.count == 1 else {return}
let data = try! Data(contentsOf: urls[0])
let subjectPDF = PDFDocument.init(data: data)
guard subjectPDF!.isLocked == false else {return}
//Find temporary array of strings
subjectPDF!.beginFindStrings(["the", "and"], withOptions: .caseInsensitive)
//Trigger results readout upon search competion
NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)
}
//Readout found strings to temporary label
#objc func onDidFindMatch(_ notification: Notification) {
resultsLabel.text = "\(String(describing: matchesFound?.string))"
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
}
The question was asked 7 months ago though, I hope you already found the solution.
Anyway the solution for your problem:
1- Move the line below to the viewDidLoad(), because you are adding an observer after triggering the beginFindString() method.
NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)
2- You are never assigning any value to matchesFound variable, so it's always nil.
3- To get the matches from beginFindString method, you need to add an observer for PDFDocumentDidFindMatch and get the data from userInfo instead of PDFDocumentDidEndFind.
PDFDocumentDidEndFind observer will be called when searching has been finished, you can use this observer for removing you loading view for instance.
Here is a sample code of the correct implementation:
var matchesFound = [PDFSelection]()
// MARK: Life cycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(didFindMatch(_:)), name: NSNotification.Name.PDFDocumentDidFindMatch, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// This method will get called every-time a match has been found.
#objc private func didFindMatch(_ sender: Notification) {
guard let selection = sender.userInfo?["PDFDocumentFoundSelection"] as? PDFSelection else { return }
self.matchesFound.append(selection)
}

Calling function from another ViewController in swift

I have already looked in Stackoverflow but I can't get an answer. I want to create function that stop playing the sound in another ViewController. But when I clicked the stop button, it cracked and showed "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)". This is my code.
First ViewController
import UIKit
import AVFoundation
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
}
and another Viewcontroller is
import UIKit
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
#IBAction func stopBtnPressed(_ sender: Any) {
metronomePlay.metronome.stop() //"EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)"
}
}
As of swift 4.1 today, this code worked for me:
Put this in sending controller:
NotificationCenter.default.post(name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
Put this in receiving controller viewDidLoad() or viewWillAppear():
NotificationCenter.default.addObserver(self, selector: #selector(disconnectPaxiSocket(_:)), name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
and then the following function in your receiving controller class:
#objc func disconnectPaxiSocket(_ notification: Notification) {
ridesTimer.invalidate()
shared.disconnectSockets(socket: self.socket)
}
Swift 5:
Put this in the Action
NotificationCenter.default.post(name: Notification.Name("NewFunctionName"), object: nil)
Put this in viewdidload() in a different viewcontroller (where is the function you want to use)
NotificationCenter.default.addObserver(self, selector: #selector(functionName), name: Notification.Name("NewFunctionName"), object: nil)
The function
#objc func functionName (notification: NSNotification){ //add stuff here}
I hope I was helpful
You are creating a NEW copy of FirstVC and calling stop on something that is not yet initialised.
You should really use a delegate in this case, something like
protocol controlsAudio {
func startAudio()
func stopAudio()
}
class FirstVC: UIViewController, controlsAudio {
func startAudio() {}
func stopAudio() {}
// later in the code when you present SecondVC
func displaySecondVC() {
let vc = SecondVC()
vc.delegate = self
self.present(vc, animated: true)
}
}
class SecondVC: UIViewController {
var delegate: controlsAudio?
// to start audio call self.delegate?.startAudio)
// to stop audio call self.delegate?.stopAudio)
}
So you are passing first VC to the second VC, so when you call these functions you are doing it on the actual FirstVC that is in use, rather than creating a new one.
You could do this without protocols if you like by replacing the var delegate: controlsAudio? with var firstVC: FirstVC? and assigning that, but I wouldn't recommend it
I use this way to call my functions from another viewControllers:
let sendValue = SecondViewController();
sendValue.YourFuncion(data: yourdata);
You can call function from other viewControllers in many ways.
Two ways that are already discussed above are by delegates & protocols and by sending notifications.
Another way is by passing closures to your second viewController from firstVC.
Below is the code in which while segueing to SecondVC we pass a closure to stop the metronome.
There will be no issue because you are passing the same firstVC (not creating a new instance), so the metronome will not be nil.
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
let secondVC = SecondVC()
secondVC.stopMetronome = { [weak self] in
self?.metronome.stop()
}
present(secondVC, animated: true)
}
}
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
var stopMetronome: (() -> Void)? // stopMetronome closure
#IBAction func stopBtnPressed(_ sender: Any) {
if let stopMetronome = stopMetronome {
stopMetronome() // calling the closure
}
}
}
var metronomePlay = FirstVC()
you are creating a new instance on FirstVC, instead you should perform the function on the same instance that of already loaded FirstVC.
Updating #Scriptable's answer for Swift 4
Step 1 :
Add this code in your view controller, from which you want to press button click to stop sound.
#IBAction func btnStopSound(_ sender: AnyObject)
{
notificationCenter.post(name: Notification.Name("stopSoundNotification"), object: nil)
}
Step 2:
Now its final step. Now add this below code, to your result view controller, where you want to automatically stop sound.
func functionName (notification: NSNotification) {
metronomePlay.metronome.stop()
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "functionName",name:"stopSoundNotification", object: nil)
}
You are initialising metronome in viewDidLoad method of FirstVC.
In SecondVC, you are initialising metronomePlay as a stored property, but never asking for ViewController's view and thus viewDidLoad of FirstVC is not getting called which results in metronome(stored property) not getting initialised.
You initialize metronome on FirstVC in viewDidLoad, which won't happen until you load the view of metronomePlay instantiated in SecondVC.
You have to call _ = metronomePlay.view, which will lazily load the view of SecondVC and subsequently execute viewDidLoad, before actually calling metronomePlay.metronome.
Try this in SecondVC. var metronomePlay = FirstVC().metronome
Either use the notification process to stop from anywhere or use same FirstVC instance from SecondVC class.

Safari ViewController with enabled address bar

I'm using Safari ViewController and address bar is disabled. How can I make it enabled so user can enter other url?
Here is the code that I'm using
class ViewController: UIViewController, SFSafariViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showSafariVC(_ sender: AnyObject) {
let svc = SFSafariViewController(url: NSURL(string: "http://www.google.com") as! URL)
svc.delegate = self
present(svc, animated: true, completion: nil)
}
}
It is unable to edit SFSafariViewController 's URL as described here.
And in Apple developer site they mentioned that too:
A read-only address field with a security indicator and a Reader
button
Hope this help!

iOS UI get stuck for a few seconds after UIWebview loading starts

The iOS UI animation gets stuck for a few seconds while loading webview(url request).
The UIWebview is a background view, and the UIAnimation is for a Popup view.
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "http://www.nyforseniors.com") {
let urlRequest = URLRequest(url: url)
self.webView.loadRequest(urlRequest)
}
NotificationCenter.default.addObserver(forName: Notification.Name("SHOW_QUIZ"), object: nil, queue: nil, using: { notification in
self.performSegue(withIdentifier: "sid_quiz", sender: self)
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if Manager.sharedInstance.isFirstLoad{
Manager.sharedInstance.isFirstLoad = false
self.openQuiz()
}
}
func openQuiz() {
self.performSegue(withIdentifier: "sid_welcome", sender: self)
}
How do I solve this problem?

Protocols and Delegates in Swift

I have two View Controllers: "DiscoverViewController" and "LocationRequestModalViewController".
The first time a user opens the "DiscoverViewController", I overlay "LocationRequestModalViewController" which contains a little blurb about accessing the users location data and how it can help them.
On the "LocationRequestModalViewController" there are two buttons: "No thanks" and "Use location". I need to send the response from the user back to the "DiscoverViewController"
I have done some research and found that delegates/protocols are the best way to do it, so I followed a guide to get that working, but I'm left with 2 errors and can't figure them out.
The errors are:
On DiscoverViewController
'DiscoverViewController' is not convertible to 'LocationRequestModalViewController'
On LocationRequestModalViewController
'LocationRequestModalViewController' does not have a member name 'sendBackUserLocationDataChoice'
I've marked where the errors are happen in the following files:
DiscoverViewController.swift
class DiscoverViewController: UIViewController, UITextFieldDelegate, CLLocationManagerDelegate, LocationRequestModalViewControllerDelegate {
func showLocationRequestModal() {
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var locationRequestVC: AnyObject! = storyboard.instantiateViewControllerWithIdentifier("locationRequestVC")
self.presentingViewController?.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.tabBarController?.presentViewController(locationRequestVC as UIViewController, animated: true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let vc = segue.destinationViewController as LocationRequestModalViewController
vc.delegate = self //This is where error 1 happens
}
func sendBackUserLocationDataChoice(controller: LocationRequestModalViewController, useData: Bool) {
var enableData = useData
controller.navigationController?.popViewControllerAnimated(true)
}
override func viewDidLoad() {
super.viewDidLoad()
showLocationRequestModal()
}
}
LocationRequestModalViewController
protocol LocationRequestModalViewControllerDelegate {
func sendBackUserLocationDataChoice(controller:LocationRequestModalViewController,useData:Bool)
}
class LocationRequestModalViewController: UIViewController {
var delegate:LocationRequestModalViewController? = nil
#IBAction func dontUseLocationData(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func useLocationData(sender: AnyObject) {
delegate?.sendBackUserLocationDataChoice(self, useData: true) // This is where error #2 happens
}
override func viewDidLoad() {
super.viewDidLoad()
//Modal appearance stuff here...
}
}
The answer is in your question itself. Both errors tells the exact reason.
Issue 1
let vc = segue.destinationViewController as LocationRequestModalViewController
vc.delegate = self //This is where error 1 happens
The self is of type DiscoverViewController
But you declared the delegate as:
var delegate:LocationRequestModalViewController? = nil
You need to change that to:
var delegate:DiscoverViewController? = nil
Issue 2
The same reason, LocationRequestModalViewController does not confirm to the LocationRequestModalViewControllerDelegate, change the delegate declaration.
You have defined your delegate as having type LocationRequestModalViewController which does not conform to LocationRequestModalViewControllerDelegate.

Resources