How to dismiss all presented view controllers when user KILL the app? - swift

I am making a twitter-like app for assignment and have to make the app stay logged in until user log out.
I have "Login View Controller" which has a segue to "Home Table View Controller".
In my loginviewcontroller.swift, I have following code
override func viewDidAppear(_ animated: Bool) {
if UserDefaults.standard.bool(forKey: "userLoggenIn") == true{
self.performSegue(withIdentifier: "loginToHome", sender: self)
}
}
After I login the app (making UserDefaults.standard.bool to be true and go to Home table view), I kill the app and re-open the app, but the viewDidAppear function is not called. I guess this is because the Home table view is not dismissed? How do I fix it?

I will suggest you to Use scene delegate method like
func sceneDidDisconnect(_ scene: UIScene) { }
or
func sceneDidEnterBackground(_ scene: UIScene) { }
like so you can save some data. I usually save Context for CoreData.

Related

how can i fix the error "Thread 1: signal SIGABRT"?

I have created a ViewController with a view and a button. I have declared the button as an outlet and as an Action in the viewcontroller. Additionally I am passing the data of the first Viewcontroller to the second Viewcontroller with a segue. When I click the cancel button which executes a segue back to the first viewcontroller I get the error
Thread 1: signal SIGABRT.
The error is shown in the prepare function which is passing the data. I have already controlled the connection inspector. I don't know what is wrong with my code.
here is my code:
#IBAction func cancelButton(_ sender: Any) {
performSegue(withIdentifier: "backToFirstScreen", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let tableViewVC = segue.destination as! tableViewController
tableViewVC.passwordNotes3 = passwordNotes2
tableViewVC.passwordCategory3 = passwordCategory2
tableViewVC.passwordStrings3 = passwordStrings2
}
I think the problem here is that you havent created a "backToFirstScreen" segue that starts in the second view controller and ends in the first viewcontroller.
Using a segue to go back to the first screen is slightly taboo; I would replace
performSegue(withIdentifier: "backToFirstScreen", sender: self)
with
self.dismiss(animated: true, completion: nil)
or if you're using a navigation controller
self.navigationController?.popViewController(animated: true)
This allows you to dismiss the current viewcontroller youre looking at and return to the view controller which presented it without having to create a segue that goes in reverse. Just make sure you have a segue that goes from the first view controller to the second view controller in that order.

Swift: How to dismiss a ViewController programmatically?

I got a little problem.
On my main view controller I got a bar button that opens a slide menu, which is a regular view controller using a slide in transition. The slide menu has a button to open another view controller. When the new view controller is opened, you have the option to cancel, which dismisses the current view controller. The problem is, that the user ends up in the menu view once again, instead of the main view controller. Would be very happy to know what I am doing wrong :)
func openSupport() {
guard let creditViewContoller = storyboard?.instantiateViewController(withIdentifier: "support") as? CreditViewController else { return }
present(creditViewContoller, animated: true)
}
#IBAction func buttonSupport(_ sender: UIButton) {
let menuView = MenuViewController()
menuView.dismiss(animated: true, completion: nil)
openSupport()
print("Tap on Support")
}
you can dismiss view controller simply by using
self.dismiss(animated: true, completion: nil)
Consider
#IBAction func buttonSupport(_ sender: UIButton) {
let menuView = MenuViewController() // (1)
menuView.dismiss(animated: true, completion: nil) // (2)
openSupport() // (3)
print("Tap on Support")
}
This:
Creates new MenuViewController but never presents it;
Calls dismiss on view controller that was never presented; and
Calls openSupport from this MenuViewController instance (which was never dismissed).
Bottom line, you want to let the main view controller that presented the menu do the presenting. So, the menu view controller should:
Define a protocol for it to inform the presenting view controller to transition to the next scene:
protocol MenuViewControllerDelegate: class {
func menu(_ menu: MenuViewController, present viewController: UIViewController)
}
And then the menu view controller can, when it’s done dismissing, tell its delegate what it should present:
class MenuViewController: UIViewController {
weak var delegate: MenuViewControllerDelegate?
#IBAction func didTapSupport(_ sender: Any) {
dismiss(animated: true) {
guard let controller = self.storyboard?.instantiateViewController(withIdentifier: "support") else { return }
self.delegate?.menu(self, present: controller)
}
}
#IBAction func didTapCancel(_ sender: Any) {
dismiss(animated: true)
}
}
Then the main view controller needs to
Make sure to set the delegate of the menu view controller:
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? MenuViewController {
destination.delegate = self
}
}
}
and
Make sure to present the view controller that the menu controller asked it to:
extension ViewController: MenuViewControllerDelegate {
func menu(_ menu: MenuViewController, present viewController: UIViewController) {
present(viewController, animated: true)
}
}
There are lots of different ways of achieving this, so don’t get lost in the details here. But the idea is to have some system by which the menu view controller can request whomever is to present the support view to do so, not try to do it itself.

Button in view doesn't work from view container

I have a view container in my view controller, which is stretched to whole screen. User interaction enabled is on. When I open another view via this view container in this view controller, button is clickable but doesn't respond. What should I do to make it work?
ViewController:
class ViewController: UIViewController {
#IBOutlet weak var viewContainer: UIView!
var views: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
views = [UIView]()
views.append(LoginVC().view)
views.append(RegisterVC().view)
for v in views{
viewContainer.addSubview(v)
}
viewContainer.bringSubview(toFront: views[1])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func switchViewAction(_ sender: UISegmentedControl) {
print("clicked - segment")
self.viewContainer.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
and this is the button in the another view
#IBAction func registerTapped(_ sender: UIButton) {
print("Clicked - register")
}
I used Managing View Controllers tutorial to make it working.
You have to replace the entire ViewController, not just the View.
#IBAction func switchViewAction(_ sender: UISegmentedControl) {
self.viewContainer.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
Your current code is just replacing LoginVC's view with RegisterVC's view, instead of replacing LoginVC with RegisterVC. When you do that, you leave behind the ViewController in charge of actually responding to any interaction.
You'll have to rework your code to keep track of the ViewControllers instead of just their views. Then, instead of adding all of your views to viewContainer and moving them to the front when you need to change the current view, you should set the your child ViewController (the ViewController embedded in your container) to the ViewController you want to show (either LoginVC or RegisterVC).

Why does this UITableViewController dismiss modally?

I have a UITableViewController that, when presented from one screen, is presented with the standard 'show' segue from right to left, and when presented from another screen (a UIViewController), is presented modally from the bottom. I got this to work properly with the help I got from a question I asked a few months ago (has screenshots).
The key was creating the segue from the UINavigationController of my Settings screen to the shared UITableViewController instead of creating it from the UITableViewCell. Strangely though, even though it presents correctly from right to left, dismissing it closes it modally (top to bottom).
I'm making the presenting table view controller a delegate of the UITableViewController it's presenting so it will handle the dismissal. Here's the protocol and extension it implements (Swift 2.3):
protocol DismissalDelegate : class {
func selectionDidFinish(controller: UIViewController)
}
extension DismissalDelegate where Self: UIViewController {
func selectionDidFinish(viewController: UIViewController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
And I set it in the segue defined in the presenting controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "goToLifts" {
let destination = segue.destinationViewController as! LiftSelectionTableViewController
destination.dismissalDelegate = self
} else {
return
}
}
The presented table view controller calls delegate?.selectionDidFinish(self) when the user makes a selection (in didSelectRowAtIndexPath):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
guard (dismissalDelegate != nil ) else {
return
}
dismissalDelegate?.selectionDidFinish(self)
}
That calls this method in the presenting table view controller:
func selectionDidFinish(controller: LiftSelectionTableViewController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
I've looked through the APIs for presenting view controllers and haven't been able to find anything that exposes options to control this. The dismiss(animated:completion:) API even says it's for dismissing a view controller presented modally, but I don't see anything else having to do with dismissal.
How can I get this thing to dismiss the same way it's presented when it's presented from my UITableViewController (right to left, and back) but keep the modal behavior when presented from the other view (a UIViewController)?
I'm a little confused here, it looks like you are using the delegate because the presenting view controller should know how LiftSelectionTableViewController got presented.
So in the table view controller, you would have
func selectionDidFinish(viewController: UIViewController) {
self.navigationController?.popViewControllerAnimated(true)
}
In the other view controller, you should have
func selectionDidFinish(viewController: UIViewController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
If I'm wrong and you can't know how the view controller was presented, then I would try checking to see if the top view controller on the navigation controller is presented view controller. Pop the view controller if it is, dismiss the view controller if it isn't.
func selectionDidFinish(viewController: UIViewController) {
if self.navigationController?.topViewController == viewController {
self.navigationController?.popViewControllerAnimated(true)
} else {
self.dismissViewControllerAnimated(true, completion: nil)
}
}

unwind segue not triggering

I have been learning swift and have made the foundation of most of my app. I have the following storyboard
app storyboard
Everything works fine. For example, I have an unwind segue on the add course view controller that triggers when you press save and you are returned to the 'your courses' view controller.
When you are on the my courses view controller, you can select a course and the topics are displayed, you can then select a topic and you are taken to an update score view controller, this all works fine.
However, my problem is this. I want to make it so that when you select save in the updatescore view controller, an unwind segue is triggered (the same as in the add course) and you are returned to the list of topics in the topics view controller.
However, I have followed many tutorials and obviously got it working before. (My action method for the unwind segue is in the correct topics view controller) but when i press save, the unwind segue is not returning me to the topics view controller.
Could anyone suggest a reason for this? I have spent a lot of time trying to find an answer and gone through many tutorials but have not managed to solve it.
I have also included a screen shot of the connections of the triggered segues for my save button to show that it is set up. Showing triggered segue for save button
i have the following code in the update score view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if saveButton === sender {
print("save button selected")
}
}
But even this is not getting triggered when I click on save.
Many thanks
UPDATE:
After following Ronatorys advice My view controller for the update score is as follows but it is still not working:
import UIKit
class UpdateScoreTableViewController: UITableViewController {
#IBOutlet weak var topicGettingUpdated: UITextField!
#IBOutlet weak var newScore: UITextField!
#IBOutlet weak var saveButton: UIBarButtonItem!
var index:Int?
var Topics:[String]!
var TopicToUpdate:String?
override func viewDidLoad() {
super.viewDidLoad()
}
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 prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let uiBarButtonItem = sender as? UIBarButtonItem else {
print("There is no UIBarButtonItem sender")
return
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0 && indexPath.row == 0 {
newScore.becomeFirstResponder()
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
But the prepare for segue is not even getting triggered.
Like Craig in the comments said, it's not that easy to find the problem. So I just build a simple app where you can follow the steps as guide and see if you forgot something to setup the functionality right. Hope it will help you. Note: Code is in Swift 3.0, but should be easy to adopt to 2.*
1. Storyboard with two View Controllers:
2. Declare the action method for the unwind segue in the FirstViewController.swift:
class FirstViewController: UIViewController {
// action method for the unwind segue
#IBAction func updateScore(_ segue: UIStoryboardSegue) {
print("Back in the FirstViewController")
}
}
3. Connect the Save button in the Storyboard with the action method (with ctrl + drag):
4. Connect your Save button with the SecondViewController.swift file, to use it for checking in your prepareSegue method (with ctrl + drag):
5. Add the prepare(for:sender:) method to your SecondViewController.swift:
class SecondViewController: UIViewController {
#IBOutlet weak var saveButtonPressed: UIBarButtonItem!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// check safely with guard that your save button is the sender and you can use it
// if not print message
guard let uiBarButtonItem = sender as? UIBarButtonItem else {
print("There is no UIBarButtonItem sender")
return
}
// check if you selected the save button
if saveButtonPressed == uiBarButtonItem {
print("save button selected")
}
}
}
Result:
The sample app you can find here
I did not manage to get the unwind segue to work but instead used
navigationController!.popViewControllerAnimated(true)
as a work around and this works fine.