I have login few screens and controllers in my app. First screen is screen with button and moves user to next login view with username, password field and login button. On the controller i have function onClickButton and when i have good data i request to the server with this data.
When server give me callback i have many params about user to set in label in next view.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
My code is
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "afterLoginView" {
if let secondVC = segue.destination as? TabBarViewController {
secondVC.finalName = self.username
}
}
}
When i want transfer my data directly to tableViewController i have error
Thread 1: signal SIGABRT
I do not understand what I'm doing wrong
You'll need these values in almost all view controllers. Create a singleton class to store the logged in user values like this
class UserDetails: NSObject, Codable {
static let shared = UserDetails()
private override init() {
super.init()
}
var finalName: String?
var otherDetails: String?
}
Now when you receive the response from the login api, assign the values in this singleton class.
UserDetails.shared.finalName = "something"//Name received from server callback
Now you can access these values from any view controller.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(UserDetails.shared.finalName)
}
}
You have some work to do to get to the right view controller. Since your segue is only pointing at the UITabBarViewController, you should put in another guard or if/let statement to get you to the UINavigationController, and then another to finally get you to the UITableViewController, where you can actually refer to your finalName variable.
That would look something like:
if let secondVC = segue.destination as? TabBarViewController {
if let navCon = secondVC.viewController[0] as? UINavigationController {
if let tableVC = navCon.topViewController as? nameOfYourTableVC {
tableVC.finalName = self.username
}
The code is untested, just typed off the top of my head, so please proceed with due caution. Issues such as which tab is the correct NavController would also need to be addressed.
You need to use the actual name of your tableView class in that last if/let. A generic UITableViewController will not include your custom variables.
When server give me callback i have many params about user to set in label in next view.
This is a great example of why you should keep the M in MVC. When you get a response back from the server, store the returned data in your data model. (If you don't have a data model, you should make one.) When a view controller gets some data from the user, such as a user name, it should store that in the model. There's little reason to pass raw data back and forth between view controllers directly... just make sure that all your view controllers have a reference to the model, and have them get and set values there as needed.
This kind of approach will make your code a lot more flexible. It allows view controllers to worry about what they need to do their job, and it gets them out of the business of caring what other view controllers need.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
It might make more sense to load the tab bar controller and then present the login view controller(s) modally. The view controllers that are managed by the tab bar controller can all be set up to refuse to do anything useful until the data they need is present in the data model, and that lets the tab bar controller be the root view controller. That will make it easy to set the model for each of it's child view controllers when the app starts up, and the app can then present the modal login view controllers, also set up with references to the model.
Related
I have three views, each with its own view controller: VC1, VC2, VC3.
The user will frequently switch back and forth between each of the three views, both forward and backward.
Each view contains data: both shared from the previous view and data unique to that view.
When the user goes back to a View that he has already visited, the data displayed on that view needs to be retained (the same data as he saw the last time he visited that view), and not set to the default values the first time he visited the view.
In the first view controller, VC1, I am using a prepare for segue to push data from VC1 to VC2 or VC3:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueToVC2” {
let destinationViewController: VC2 = segue.destination as! VC2;
destinationViewController.passedData1 = firstAmount
destinationViewController.passedData2 = secondAmount
destinationViewController.passedData3 = thirdAmount
} else {
let destinationViewController: VC3 = segue.destination as! VC3;
destinationViewController.passedData1 = firstAmount
destinationViewController.passedData2 = secondAmount
destinationViewController.passedData3 = thirdAmount
destinationViewController.passedData4 = fourthAmount
}
By tapping the GO BACK button on each view, I return to the previous view:
#IBAction func goBackButtonPressed(_ sender: Any) {
print("Back Button Pressed!")
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
I am having trouble passing data backwards. And when I return to VC2 from VC1, data has been reset to 0. I have no segues going back from VC2 to VC1 or from VC3 to VC2. Would that be the cleanest way to pass the data back: to create another segue in Main.storyboard from VC2 to VC1 and then add another ‘if’ to my prepare for segue that checks for VC1?
I am passing ALL these variables back and forth between view controllers but only using some of them in each view controller. It seems like a waste and I don't think I am on the right track here.
Any help or suggestions?
View controllers should never store data. They are responsible for coordinating between model objects and view objects. That's their whole point. The pattern you're looking for is called MVC (Model-View-Controller) and it's a core part of iOS development.
Move your data out of the view controllers and put it into model classes. Each view controller should fetch data out of the model, and send updates into the model. The only thing the view controllers should pass between themselves is what model objects to work on, and most of the time that only needs to pass in one direction (down the stack).
Delegation can be a useful tool here, and you can also investigate "unwind segues" which are built to help you send data upstream. But again, the data you should be sending is mostly references to the model, and the model itself needs to live outside the view controllers.
It's in Objective-C, but still one of the best simple examples from Apple on MVC design is TheElements, and is worth exploring as a basis. Even without reading the Objective-C, you can see how the various pieces fit together.
I haven't studied it as much as TheElements, but Lister claims to be a good demonstration of MVC patterns in Swift using modern iOS techniques.
Why don't you call a delegate which passes the data to the view controller when you press back button.
Or if the data shared by all view controllers reflect the same value. Make a singleton class and use those values across the app.
example singleton class:
class SomeModel {
static let shared = SomeModel()
private init() {}
}
When we move the a segue from A view to B view or from b view to A view, viewdidload get data from server all the time
So, can we keep the data that viewdidload get at first excute, without starting viewdidload each page?
It's waste of time to get data all the time, when we open each page.
From now we using pageviewcontroller, I think it is inappropriate it.
I using swift language.
If you have good idea, please let me know.
There are very many tutorials on the internet that show how to do what you want. Try a google search on swift pass data between view controllers.
At its very simplest, the second controller needs to have one or properties that can hold data from the first controller. The first controller must implement the following function:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let secondVC = segue.destinationViewController as! ViewControllerClass
// Pass data to the second view controller.
secondVC.dataFromServer = dataFromServer
secondVC.otherData = otherData // etc.
}
The data will then be passed from the first controller to the second.
The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.
I'm new to swift and am trying to write an app with it.
I have a UIViewController that I am transitioning to. I have designed the UI in interface builder and I intend to use segues to manage the transition. However, the view controller relies on data that is passed into the view controller from the previous view controller.
If I have properties on my view controller then I will need to redefine my init method. But I wouldn't normally call the init method; it would be called for me before prepareForSegue. So I see a few possible solutions:
Make my variables optional (so I can pass them in prepareForSegue
and update the view then).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController: EventViewController = segue.destinationViewController as! EventViewController
viewController.event = self.event
}
Manually init my view controller and present it programmatically instead.
???
Is there a third option? If not, which of the previously mentioned 2 is better practice?
There is two possible options as you mentioned:
The first one is the easiest which is to pass the data in prepareForSegue. which you don't have to care about dismissing the controller or keeping a track of inner view controllers,because storyboard will take care of it.
The second way is to set a Storyboard ID in storyboard,for the controller you need to present programmatically, which need more things to handle, like to dismiss the controller or keep track of inner presented controllers.
let nextViewControllerName = storyboard?.instantiateViewControllerWithIdentifier("Storyboard ID") as! nextViewControllerName
nextViewControllerName.event = self.event
self.presentViewController(nextViewControllerName, animated: true, completion: nil).
At the end they does the same purpose.
Note: You should always pass the data before presenting the controller.
I currently have a navigation controller that contains one view controller and two table view controllers. When the user navigates to the second table view controller, they can press an '+' bar button item which modally presents another table view with a static cell that contains a text field. The user can enter text and press 'Done' to save it. When my delegate method is called, the view controller is dismissed and that user-entered string is appended to the array in the second view controller. The table view then displays that user-entered text in a table cell. The array has been passed up the stack via the prepareForSegue method. This is currently working because I can see the strings in the table view that the array was instantiated with in the first view controller.
My problem occurs when the user navigates back down the stack to any view controller prior to the second table view, which contains the user-entered strings displayed in cells. I have successfully been able to pass the data to the previous view controller, obviously, but after much research and testing, I am unable to successfully get the user-entered string to persist. When I navigate to previous view controllers and then back go the second table view, the user-entered data is gone, and the array contains only the initial data.
I understand that arrays in Swift are structures and the values are copied when passed up the stack. This makes sense as to why the user-entered data disappears when I navigate to a previous view controller. The previous view controller does not actually contain that user-entered data. Does this mean that I need to append the string to the array in the initial view controller instead of the array in the second table view controller? If that is the case, I am unsure of how to access the initial view controller from the third view controller.
All of the documents and tutorials that I have accessed are based on passing the data from the current view controller back to the previous view controller, which is the delegate that then dismisses the view controller that accepted the input.
How can I pass the user-entered string from the last view controller back to the initial view controller?
Actually, there are a lot of messaging mechanisms in Objective C, and in addition to Phillip's answer I can suggest you to use NSNotificationCenter by using which your can post notifications and catch them where needed. Posted notification can have custom object, string in your case, and name, for which you should add observer in order to receive that notification.
Registering for particular notification:
[[NSNotificationCenter defaultCenter] addObserver:{object_to_handle_notification} selector:#selector(notificationReceived:) name:#"my_notification" object:nil];
Posting notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"my_notification" object:userEnteredString];
Handling notification:
- (void)notificationReceived:(NSNotification *)notification
{
NSString *userEnteredString = notification.object;
//do whatever you want with the user entered string
}
Also, you should remove observer for the notification when you are done.
Hope this was helpful. Good Luck!
Create a class that is responsible for maintaining your data model. You can either make it a singleton or have an object of the class available through your application delegate (or some other option). When a controller wants information to be available to other parts of the app, it sends it to the data model for update. When a controller wants to display information, it gets it from the data model.
No more data-dependencies between controllers that have to figure out what to exchange.
What you can do is pass the firstViewController as a parameter to the ThirdViewController in the prepareForSegue method from the SecondViewController.
FirstViewController:
class FirstViewController: UIViewController {
var a:String!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var destination=segue.destinationViewController as? SecondViewController
destination?.firstViewCont=self
}
}
SecondViewController:
class SecondViewController: UIViewController {
var firstViewCont:FirstViewController!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var destination=segue.destinationViewController as? ThirdViewController
destination?.FViewCont=self.firstViewCont
}
}
ThirdViewController:
class ThirdViewController: UIViewController {
var FViewCont:FirstViewController!
#IBAction func textFieldEdited(sender: UITextField) {
FViewCont.a!=sender.text
}
}
This is one of the most impractical ways one would do it. I suggest that you learn about Navigation View Controllers. They work like a charm when doing something like this.