I've run into this problem while trying in objective-c and now Swift. I create a view controller (vc1), embed it with a navigation controller and then finally create a new view controller (vc2). Then, I put a button on vc1, and control drag it to vc2. I run the app and everything is fine and dandy, clicking on the button will push the new view controller onto the stack and pressing the back button will take me back. However, once I proceed to add new buttons and text fields to vc2, the app will crash once I press my original button on vc1, citing the first line of the app delegate file as the breakpoint. I've tried many things, namely trying to code the segue using the self.performSegueWithIdentifier method but it does not work. I am using Xcode 6.
storyboard.swift (file for vc1):
import UIKit
class storyboard: UIViewController {
#IBOutlet var titleLabel : UILabel
#IBOutlet var orLabel : UILabel
#IBAction func weightedAverageButtonPressed(sender : AnyObject) {
self.performSegueWithIdentifier("weightedAverage1", sender: self)
}
#IBAction func whatINeedButtonPressed(sender : AnyObject) {
}
}
weightedAverage.swift (vc2)(I've commented everything out to get rid of the crash, but no luck):
import UIKit
class weightedAverage: UIViewController {
/* #IBOutlet var weightAverageTitleLabel : UILabel
#IBOutlet var percentagetoCalcLabel : UILabel
#IBOutlet var percentageLabel : UILabel
#IBOutlet var percentageInput : UITextField
#IBAction func continueButton(sender : AnyObject)
{
//var percent = percentageInput.text
// percentageLabel.text = percent
}*/
}
appdelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
return true
}
}
Here is what the debug output says (when I've commented out my IBAction method):
2014-06-19 12:29:12.471 gradeCalc[13217:580024] -[_TtC9gradeCalc10storyboard weightedAverageButtonPressed:]: unrecognized selector sent to instance 0x10bb0ad30
(lldb)
It also highlights the line of app delegate beginning in "class AppDelegate...."
When I uncomment the method which contains the performSegue... method ALL the output log says
"(lldb)" and it still highlights the line of app delegate
Have you tried removing the manual IBAction code for the button and just using the segue made on the Storyboard? It may be trying to segue twice simultaneously if you both used the Storyboard and the Class file to do it.
Edit: All the code in my working example for the two view controllers
First View Controller:
class OneViewController: UIViewController {
#IBOutlet var label1: UILabel
#IBOutlet var label2: UILabel
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
Second View Controller:
class TwoViewController: UIViewController {
#IBOutlet var upperlabel: UILabel
#IBOutlet var lowerlabel: UILabel
#IBOutlet var textbox: UITextField
#IBAction func button(sender: AnyObject) {
var percent = textbox.text
lowerlabel.text = percent
}
override func viewDidLoad() {
super.viewDidLoad()
}
Can't add an image due to NDA, but Storyboard is just NavigationController>VC1>VC2, with two labels in each VC, a text box in VC2, and a button in each. Only the second button has an action in its VC class.
Related
App has two View Controllers: ViewController (this is the main View Controller that displays the majority of the app's content) and SecondViewController (accessible via a UIButton on ViewController; SecondViewController is only used to display a user's inventory, and a UIButton within SecondViewController allows the user to return to the original view, ViewController). Currently, the app uses the "Show" action segue to switch between View Controllers when the user presses the appropriate UIButton. However, after switching from ViewController to SecondViewController, and then pressing the UIButton to return to ViewController, the properties of ViewController have been reverted to the properties that occur when the app launches (background color is changed, certain text fields appear that shouldn't).
So, how do I "save the state" of ViewController when the user moves to SecondViewController, so that the user resumes where they left off when they return to ViewController?
What you are looking for is an unwind segue. Here's the simplest way of how to create it:
In your ViewController (or, basically any other view controller you are willing to pop to) create an IBAction that accepts an instance of a segue (function name doesn't really matter):
#IBAction func unwindToThisVC(segue: UIStoryboardSegue) { }
In the storyboard, go to SecondViewController, and control + drag from your UIButton to the Exit outlet of ViewController and then select the IBAction you've created in step 1:
More on Unwind Segues
The way you are doing it now (using Show from the second to get back to the first) actually brings up a third VC.
What you want to do is dismiss the second view controller.
The normal way is to implement a protocol for the second one that the first one implements and then to have a function in that protocol for the second one to let the first one know it is done.
When the function is called, the first one dismisses the second and then it will be shown again with its state intact.
Here is a simple example of segue and unwind that you can adapt to your problem... Assume that you have ViewController with label and a button and a SecondViewController with label and a button.
For the first ViewController...
import UIKit
//steps to receive data back from SecondViewController...
//1. create protocol in the SecondViewController (see SecondViewController code)
//2. conform to the protocol
class ViewController: UIViewController, UnwindSegue {
//3. method that gets triggred.
func dataReceived(dataSegued: String) {
labelOne.text = dataSegued
}
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var labelOne: UILabel!
var textReceived : String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btPressed(_ sender: Any) {
performSegue(withIdentifier: "goToSecondController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondController" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.textSegued = textField.text!
//4. create delegate in the SecondViewController (see SecondViewController code)
//5. set ourselves up as delegate of SecondViewController
destinationVC.delegate = self
//6. then dismiss the SecondViewController (see SecondViewController code)
}
}
}
Then for your SecondViewController...
import UIKit
//1. create protocols and delegates to transfer data back
protocol UnwindSegue {
//single required method with a single parameter
func dataReceived(data:String)
}
class SecondViewController: UIViewController {
var textSegued : String?
//4. create delegate of the protocol of type CanReceive that can be a nil. If it is nil, it doesn't go anywhere when BT is pressed
var delegate : UnwindSegue?
#IBOutlet weak var label: UILabel!
#IBOutlet weak var secondTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.text = textSegued
}
#IBAction func btTwoPressed(_ sender: Any) {
//this is not triggered if var delegate is nil (as defined as optional)
delegate?.dataReceived(data: secondTextField.text!)
//6. dismiss the 2nd VC so you can see the fist VC
dismiss(animated: true, completion: nil)
}
}
I have a view controller class in my app:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var usersTable: UITableView!
var pendingRequest: FBRequest?
var pendingLoginForSlot: Int!
var userID: String?
var swipeGesture: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
usersTable.delegate = self
usersTable.dataSource = self
self.pendingLoginForSlot = -1
self.usersTable.tableFooterView = UIView(frame: CGRectZero)
}
I want this view controller to be initialized and completely functional in my app delegate after didFinishLaunchingWithOptions When I define this view controller in the app delegate like so:
var VC: ViewController = ViewController()
The VC.usersTable is nil. But when I do my function after viewDidLoad everything is working fine.
So how can I fire functions from within app delegate (e.g. logging in users that are defined in the tableview) and thus have the properties available that get defined in viewDidLoad and expect the same results after I load the VC in a regular manner by loading it via a tab barcontroller?
Thanks!
I have a tab bar class (that is attached to my tab bar controller), Like so:
class CaptionTabBarController: UITabBarController, UITabBarControllerDelegate {
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
var logView = self.viewControllers![2] as CaptionsController
logView.log.append("test working!")
}
override func awakeFromNib() {
self.delegate = self;
}
}
And my receiving viewcontroller is like this:
class CaptionsController: UIViewController {
#IBOutlet weak var captionSearchBar: UISearchBar!
#IBOutlet weak var captionsTitle: UILabel!
var receiveImage:UIImage!
var receiveCategoryText:String!
var log = [String]()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println(log)
}
}
This works when I'm explicitly setting logView.log in CaptionTabBarController.
The result I get in my output windows is as expected. Each tabbar item I click adds "test working!" to the array.
My question is:
How would I be able to get a value from another viewcontroller class to CaptionsController using the tabBarController method I am employing?
This view is a part of a "child" of the tabbar itself, so I'm assuming it already has an instance. All examples I've found just show this, but not how to get data from another class.
The UIViewController that wants to pass the data can store it on your AppDelegate class. Then the UITabBarController delegate method can pull it off and set properties on the receiving UIViewController.
Also, assuming your app is based on the Tab Controller, your AppDelegate can find it with window?.rootViewController as UITabBarController.
I want when I open my app, if a certain condition is true, the app to open to a custom SignUpViewController instead of my TabBarViewController.
What I did is in the application function in my app delegate is use the following code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//removed condition for testing
var rootViewController = self.window!.rootViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var profileViewController: SignUpViewController = mainStoryboard.instantiateViewControllerWithIdentifier("Registration") as SignUpViewController
rootViewController?.navigationController?.popToViewController(profileViewController, animated: true)
return true
}
I have named my ViewController in the storyboard and everything should work fine. but instead of opening to the SingUpViewController the app opens normally to the first tab in my TabBarViewController.
I don't get any crashes it just doesn't show.
Using: pushViewController didn't work either.
My storyboard:
Does anyone know what could be the cause of this?
EDIT:
This is what I write to present the view instead of using nav controller:
I switch out this line:
rootViewController?.navigationController?.popToViewController(profileViewController, animated: true)
With this line:
rootViewController?.presentViewController(profileViewController, animated: true, completion: nil)
When I run it, i get a crash on this line:
class AppDelegate: UIResponder, UIApplicationDelegate {
Here is an image of the crash:
EDIT:
Sorry, I am new to Xcode and Swift and iPhone development, here is the console when it crashes:
From what I can see now it refers to a loginButton from the viewController I am trying to present. I am guessing there is a problem there, ill try and fix it, but if you know what the problem is answer anyways.
EDIT:
I can see the problem lies somewhere in the new ViewController. Yet it is referring to something which does not exist.
Here is the code of the new view controller:
import UIKit
class SignUpViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBOutlet weak var emailField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func signUpPressed(sender: AnyObject) {
}
#IBAction func loginPressed(sender: AnyObject) {
}
}
rootViewController is presumably a tab bar controller. This won't have a navigation controller (since it isn't contained in one), so your optional chaining breaks down.
In any case you'd end up with the other tabs showing. I think you want the root view controller to present the sign up controller instead.
Your second problem is because you'll have connected an outlet in your storyboard to something that doesn't exist any more. If you select the SignUpViewController in the storyboard and look at the connections inspector, you should see it, there will be an exclamation mark next to it indicating that it can't be connected. Delete the connection and you should be fine.
I am trying to familiarize with swift but I can't find how to pass data between views using Swift.
class ViewController: UIViewController {
#IBOutlet var field: UITextField
#IBOutlet var butt: UIButton
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if let vc = segue.destinationViewController as? secondViewController {
if(vc.lab != nil){
vc.lab.text=self.field.text
println(self.field.text)
}
}
and second view controller:
class secondViewController: UIViewController {
#IBOutlet var lab: UILabel
override func viewDidLoad() {
super.viewDidLoad()
}
What I want to do is simply change the label in the second view with the text of the textfield of the first view.
In this way does not give me any error but I do not change the label.
To me, this doesn't look like a Swift problem. It looks like a view lifecycle problem. At the time prepareForSegue: is called, the secondViewController has not loaded it's IBOutlets from the storyboard yet. A better solution would be to set some type of property on the file, like
vc.myLabelString = self.field.text
then in viewDidLoad of secondViewController assign the text to your label.
FYI: You can always check if a view controller has loaded it's view with vc.isViewLoaded()