I'm very new with programming. Currently, I need to trigger a segue directly after a function is being executed.
This is my code:
func onlineSearch() {
let urlToGoogle = "https://www.google.com/search?q=\(resultingText)"
let urlString = urlToGoogle.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
url = URL(string: urlString!)
performSegue(withIdentifier: K.goToBrowser, sender: nil)
}
}
When I run this, I get this error:
Warning: Attempt to present <MyApp.SFSafariViewController: 0x10153be70> on <MyApp.CameraViewController: 0x101708460> whose view is not in the window hierarchy!
But if I run all the same, but simply trigger the segue from a button, instead of the way I want, it actually works perfectly.
#IBAction func webViewButtonPressed(_ sender: UIButton) { // TEMP
performSegue(withIdentifier: K.goToBrowser, sender: self)
}
P.S.: Nevermind the grotesque unwrapping, it is just working as a placeholder right now.
Reposting my comment as an answer with a code snippet -
If the caller of onlinesearch() may be on anything other than the main thread, you need to wrap the performSegue call in a DispatchQueue.main.async{} block.
DispatchQueue.main.async {
performSegue(withIdentifier: K.goToBrowser, sender: nil)
}
Generally, methods which affect UI (updating controls, performing segues, etc.) need to be on the main thread. Calls from UIKit are already on the main thread (viewDidLoad, draw, events, notifications, timers etc.), but other task completion handlers (and anything run on explicitly on a different queue) may not be.
try using this to open the safari URL
if let url = URL(string: "https://www.google.com/search?q=\(resultingText)") {
UIApplication.shared.open(url)
}
Here's a function if you want to use it anywhere else.
But you should mark Chris Comas answer as the correct one.
func openURL(url: URL) {
UIApplication.shared.open(url, options: [:])
}
}
Just for the sake of knowledge:
let urlGoogle = URL(string: "https://www.google.com")
openURL(url: urlGoogle)
if you want to open the browser inside the app check Swift documentation: https://developer.apple.com/documentation/uikit/uiwebview
Related
I have two view controllers, LoginViewController and TermsAndPrivacyViewController.
On first view controller, there is a button that has IBAction, which opens TermsAndPrivacyViewController. On TermsAndPrivacyViewController I have web view, that loads url I am passing from LoginViewController. So, this is the code(Login view controller):
#IBAction func tosAction(_ sender: Any) {
if let vc = UIStoryboard(name: "Login", bundle: nil).instantiateViewController(withIdentifier: kTOSViewControllerIdentifier) as? TermsAndPrivacyViewController{
vc.url = URL(string: kTOSUrl)
self.navigationController?.pushViewController(vc, animated: true)
}
}
On TermsAndPrivacyViewController, I have this:
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
if let `url` = url {
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
}
So, when I hit a button, app crashes SIGABRT message, and nothing else. Also, I don't get any crash report on Firebase Crashlytics (I have detached an app from a debugger first, and produced a crash).
Now, the strange part :/ ... If I put a breakpoint on a first line of tosAction() method, and go step by step (or just let it continue), everything works normally. No crash???
Am I missing something obvious here? What is the reason of crashing? Also, I tried to put Exception Breakpoint but nothing changes. I only get this:
and a console:
so, kinda no information about crash at all.
EDIT
Ok, now the even more strange part :) I just tried app on different phones.
App crashes on iPhone 6s+, iOS 12.1(16B5059d), but it works normally on iPhone 6, iOS 12.0 (16A366)
maybe you use library or framework that not supported by those device.
you must see the error
did you try this?
when the app crashing, in the console press cmd+F and search exception.
now you can see your error
hope to helpful.
Maybe you can use segue methods inside your LoginViewController.
#IBAction func tosAction(_ sender: Any) {
performSegue(withIdentifier: "GoToWeb", sender: nil)
}
}
And call prepareForSegue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "GoToWeb") {
let vc = segue.destination as! TermsAndPrivacyViewController
vc.url = "YOUR_URL"
}
And the code inside TermsAndPrivacyViewController don't change
Edit:
Maybe your not binding well your UIButton, can you verify in the right panel, in the section "Show the connection inspector" if your button is call only once.
To get further information on this type of crashes, open the Breakpoints menu (or press CMD+8), click the plus icon in the bottom left corner and press Exception Breakpoint.... Right click, then edit. Add an action of the type Debugger Command and enter the following:
Reproduce the crash again, this time the console will output a more useful error message.
I've a function in Swift like so:
#IBAction func doSomething(_ sender: AnyObject) { }
I need the sender to be of type NSMenuItem, so I check it with a guard:
guard let menuItem = sender as? NSMenuItem else { return }
But this will silently let the application continue if there is some serious error in my application logic, resulting in a different object type being passed.
Wouldn't it be better to just crash the application, rather than 'presenting' the user with a mysteriously non-working function?
What is the best way to check and react for these super-basic assumptions?
Note that you can write your #IBActions like this:
#IBAction func doSomething(_ sender: NSMenuItem)
sender does not have to be AnyObject.
In other situations though, if you want to crash with a guard statement, you could do:
guard ... else { fatalError("a message") }
fatalError returns Never, so it can be used in the else clause.
How can I have a button disappear after it's been clicked?
#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
//...a lot of blocking instructions below this line
}
The above works to a certain extent, as the "sender" / button is hidden only after all of the instructions in the function have been processed. I have some blocking IO in the function (socket connections etc.) and I want the button to disappear before all that happens.
I tried using both outlets and sender.
#IBAction func onClick(_ sender: NSButton) {
sender.isHidden = true;
DispatchQueue.main.async {
//...a lot of blocking instructions below this line
}
}
I managed to achieve the desired effect by putting the "blocking" piece of code in the following statement (and pushing the .isHidden setting through immediately, in a synchronous fashion):
DispatchQueue.main.async { /*code*/ }
I am trying to display an activity indicator whilst some text recognition happens. If i just start and stop[ the indicator around the recognition code it never shows. The issue i have is that if use:
activityIndicator.startAnimating()
DispatchQueue.main.async( execute: {
self.performTextRecognition()
})
activityIndicator.stopAnimating()
self.performSegue(withIdentifier: "segueToMyBills", sender: self)
The indicator never shows as it performs the segue and the table view in the next view controller shows no information because the text recognition hasn't completed. I've never touched on threads until now so a little insight on what to do would be much appreciated. Thanks!
Well your problem is that your OCR happens on the main thread. That blocks the thread thus never having time to draw the activity indicator. Try to modify your code into this:
activityIndicator.startAnimating()
DispatchQueue.global(qos: .background).async { [weak weaKSelf = self] in
// Use a weak reference to self here to make sure no retain cycle is created
weakSelf?.performTextRecognition()
DispatchQueue.main.async {
// Make sure weakSelf is still around
guard let weakSelf = weakSelf else { return }
weakSelf.activityIndicator.stopAnimating()
weakSelf.performSegue(withIdentifier: "segueToMyBills", sender: weakSelf)
}
}
Try to add a completion handler to your self.performTextRecognition() function in this way
function performTextRecognition(completion: ((Error?) -> Void)? = .none) {
//you can replace Error with Any type or leave it nil
//do your logic here
completion?(error)
}
and then call the function like this :
performTextRecognition(completion: { error in
activityIndicator.stopAnimating()
self.performSegue(withIdentifier: "segueToMyBills", sender: self)
})
I would like to only execute a segue if I get a certain response from the server. In swift, how can I wait until I get a response to continue?
Bottom line, you don't "wait" for the response, but rather simply specify what you want to happen when the response comes in. For example, if you want to perform a segue when some network request is done, you should employ the completion handler pattern.
The issue here is that you're probably accustomed to just hooking your UI control to a segue in Interface Builder. In our case, we don't want to do that, but rather we want to perform the network request, and then have its completion handler invoke the segue programmatically. So, we have to create a segue that can be performed programmatically and then hook your button up to an #IBAction that performs the network request and, if appropriate, performs the segue programmatically. But, note, there should be no segue hooked up to the button directly. We'll do that programmatically.
For example:
Define the segue to be between the two view controllers by control-dragging from the view controller icon in the bar above the first scene to the second scene:
Give that segue a storyboard identifier by selecting the segue and going to the "Attributes Inspector" tab:
Hook up the button (or whatever is going to trigger this segue) to an #IBAction.
Write an #IBAction that performs network request and, upon completion, programmatically invokes that segue:
#IBAction func didTapButton(_ sender: Any) {
let request = URLRequest(...). // prepare request however your app requires
let waitingView = showWaitingView() // present something so that the user knows some network request is in progress
// perform network request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// regardless of how we exit this, now that request is done, let's
// make sure to remove visual indication that network request was underway
defer {
DispatchQueue.main.async {
waitingView.removeFromSuperview()
}
}
// make sure there wasn't an error; you'll undoubtedly have additional
// criteria to apply here, but this is a start
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// parse and process the response however is appropriate in your case, e.g., if JSON:
//
// guard let responseObject = try? JSONSerialization.jsonObject(with data) else {
// // handle parsing error here
// return
// }
//
// // do whatever you want with the parsed JSON here
// do something with response
DispatchQueue.main.async {
performSegue(withIdentifier: "SegueToSceneTwo", sender: self)
}
}
task.resume()
}
/// Show some view so user knows network request is underway
///
/// You can do whatever you want here, but I'll blur the view and add `UIActivityIndicatorView`.
private func showWaitingView() -> UIView {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
effectView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(effectView)
NSLayoutConstraint.activateConstraints([
effectView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
effectView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
effectView.topAnchor.constraintEqualToAnchor(view.topAnchor),
effectView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
])
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
effectView.addSubview(spinner)
spinner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
spinner.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
spinner.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
])
spinner.startAnimating()
return effectView
}