Array.append is overwriting not appending - ios

I have two view controllers, one contains a table view with two sections where I display a list of movies, and another view controller where I can add movies. Assume the view controller that contains the table view is VC1 and the add movie is VC2.
The problem is when I add a movie in VC2 and append it to the list of movies in VC1, it just replaces the previously added movie.
I only add the movies locally during runtime. So whenever I run the app the list initially contains 0 movies. So I add a movies and then append it to the list. When I try to add another movie, it replaces the one before it as if the list cannot contain more than one item.
This is where I append the array:
#IBAction func addMovieButtonClicked(_ sender: UIButton) {
if imageView.image == nil || movieTitleField.text == "" || movieDateField.text == "" || movieOverviewField.text == "" {
displayAlert(title: "Warning", message: "Please enter all data to be able to add your movie!")
} else {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
let movie = Movie(title: movieTitleField.text!, overview: movieOverviewField.text!, date: movieDateField.text!)
vc.customMovies.append(movie)
vc.customImages.append(imageView)
self.navigationController?.pushViewController(vc, animated: true)
}
}
but when I move back to VC1 I find the data is replaced and not appended. Any solutions?
Edit:
The first view controller that contains the tableView that I show the data is called ViewController. The second one where I add a movie is called AddMovie.
This is how I navigate to the AddMovie:
#IBAction func addMovieButton(_ sender: UIBarButtonItem) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "AddMovie") as! AddMovie
self.navigationController?.pushViewController(vc, animated: true)
}

You are creating new instence every time ...
self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
You need to get the current and then append. To append movies you can use delegates as well as Closures
To use Delegate
protocol AddMoviesToController:AnyObject {
func addMoviesToController(movie:Movie , image:UIImage)
}
class AddMovie:UIViewController {
weak var delegate:AddMoviesToController?
#IBAction func addMovieButtonClicked(_ sender: UIButton) {
if imageView.image == nil || movieTitleField.text == "" || movieDateField.text == "" || movieOverviewField.text == "" {
displayAlert(title: "Warning", message: "Please enter all data to be able to add your movie!")
} else {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
let movie = Movie(title: movieTitleField.text!, overview: movieOverviewField.text!, date: movieDateField.text!)
delegate?.addMoviesToController(movie: movie, image: imageView)
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
write extension like this to viewController
extension ViewController: AddMoviesToController {
func addMoviesToController(movie:Movie , image:UIImageView) {
//add movies here
customMovies.append(movie)
customImages.append(image)
tableView.reloadData()
}
}
And while pushing your controller
class ViewController: UIViewController {
#IBAction func addMovieButton(_ sender: UIBarButtonItem) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "AddMovie") as! AddMovie
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
}

Related

Why I get a uncaught expetion when I want save a value

I'm just learning XCode and now I have the following problem, I've tried a few things but I can't get any further at this point, does anyone have an idea of what this could be.
I have two ViewControllers. With the first ViewController you have the possibility to open a popup for user input via a button (second ViewControler). After entering the user, you can save the user with a save button and you should get back to the main page. Then I always get the error
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PwGen.ViewControllerUser tfUser:]: unrecognized selector sent to instance "
///////////////////////////Button save
#IBAction func btnConfirm(_ sender: UIButton)
{
save()
let story = UIStoryboard(name: "Main", bundle: nil)
let controller = story.instantiateViewController(withIdentifier: "ViewController") as! ViewController
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: false, completion: nil)
}
///////////////////////////function save
func save()
{
let strEingabe = tfUser.text
let defaults = UserDefaults.standard
defaults.set(strEingabe, forKey: "customTextUser")
defaults.synchronize()
let strtest = defaults.string(forKey: "customTextUser")
print(strtest)
}
Thanks a lot
Hi guys,
I've tried a few more things, but without success
Here is the code of my second ViewControll. If I don't enter anything in the "tfUser" text field, everything works as desired and the first view controller is opened and the code of the first view is processed. However, if you enter something in "tfUser", the value is saved, the first ViewController is opened and then crashes, although the same code is processed as if you left "tfUser" empty
i don't know why its working when "tfUser" is empty to and string in "tfUser".
import UIKit
class ViewControllerUser: UIViewController {
#IBOutlet weak var tfUser: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
}
//Button abort
#IBAction func btnBack(_ sender: UIButton)
{
tfUser.text = ""
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
//Button confirm
#IBAction func btnConfirm(_ sender: UIButton)
{
save()
let vc = storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
//function save
func save()
{
let strEingabe = ("\(tfUser.text ?? "")")
let defaults = UserDefaults.standard
defaults.set(strEingabe, forKey: "customTextUser")
tfUser.text = ""
}
}

ViewController Pushing Swift From One VC to Another VC And Returning back

Consider two view controller Controller1 and Controller2, I have created a form of many UITextField in controller 1, in that when a user clicks a particular UITextField it moves to Controller2 and he selects the data there.
After selecting the data in Controller2 it automatically moves to Controller1, while returning from controller2 to controller1 other UITextfield data got cleared and only the selected data from controller2 is found. I need all the data to be found in the UITextfield after selecting.
Here is the code for returning from Controller2 to Controller1
if(Constants.SelectedComplexName != nil)
{
let storyBoard: UIStoryboard = UIStoryboard(name: "NewUserLogin", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "NewUser") as! NewUserRegistrationViewController
self.present(newViewController, animated: true, completion: nil)
}
To pass messages you need to implement Delegate.
protocol SecondViewControllerDelegate: NSObjectProtocol {
func didUpdateData(controller: SecondViewController, data: YourDataModel)
}
//This is your Data Model and suppose it contain 'name', 'email', 'phoneNumber'
class YourDataModel: NSObject {
var name: String? //
var phoneNumber: String?
var email: String?
}
class FirstViewController: UIViewController, SecondViewControllerDelegate {
var data: YourDataModel?
var nameTextField: UITextField?
var phoneNumberTextField: UITextField?
var emailTextField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
callWebApi()
}
func callWebApi() {
//After Success Fully Getting Data From Api
//Set this data to your global object and then call setDataToTextField()
//self.data = apiResponseData
self.setDataToTextField()
}
func setDataToTextField() {
self.nameTextField?.text = data?.name
self.phoneNumberTextField?.text = data?.phoneNumber
self.emailTextField?.text = data?.email
}
func openNextScreen() {
let vc2 = SecondViewController()//Or initialize it from storyboard.instantiate method
vc2.delegate = self//tell second vc to call didUpdateData of this class.
self.navigationController?.pushViewController(vc2, animated: true)
}
//This didUpdateData method will call automatically from second view controller when the data is change
func didUpdateData(controller: SecondViewController, data: YourDataModel) {
}
}
class SecondViewController: UIViewController {
var delegate: SecondViewControllerDelegate?
func setThisData(d: YourDataModel) {
self.navigationController?.popViewController(animated: true)
//Right After Going Back tell your previous screen that data is updated.
//To do this you need to call didUpdate method from the delegate object.
if let del = self.delegate {
del.didUpdateData(controller: self, data: d)
}
}
}
push your view controller instead of a present like this
if(Constants.SelectedComplexName != nil)
{
let storyBoard: UIStoryboard = UIStoryboard(name: "NewUserLogin", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "NewUser") as! NewUserRegistrationViewController
self.navigationController?.pushViewController(newViewController, animated: true)
}
and then pop after selecting your data from vc2 like this
self.navigationController?.popViewController(animated: true)
and if you are not using navigation controller then you can simply call Dismiss method
self.dismiss(animated: true) {
print("updaae your data")
}
There are a few ways to do it, but it usually depends on how you move from VC#1 to VC#2 and back.
(1) The code you posted implies you have a Storyboard with both view controllers. In this case create a segue from VC#1 to VC#2 and an "unwind" segue back. Both are fairly easy to do. The link provided in the comments does a good job of showing you, but, depending on (1) how much data you wish to pass back to VC#1 and (2) if you wish to execute a function on VC#2, you could also do this:
VC#1:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowVC2" {
if let vc = segue.destination as? VC2ViewController {
vc.VC1 = self
}
}
}
VC#2:
weak var VC1:VC1ViewController!
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParentViewController {
VC1.executeSomeFunction()
}
}
Basically you are passing the entire instance of VC1 and therefore have access to everything that isn't marked private.
(2) If you are presenting/dismissing VC#2 from VC#1, use the delegate style as described by one of the answers.
VC#1:
var VC2 = VC2ViewController()
extension VC1ViewController: VC2ControlllerDelegate {
func showVC2() {
VC2.delegate = self
VC2.someData = someData
present(VC2, animated: true, completion: nil)
}
function somethingChanged(sender: VC2ViewController) {
// you'll find your data in sender.someData, do what you need
}
}
VC#2:
protocol VC2Delegate {
func somethingChanged(sender: VC2ViewController) {
delegate.somethingChanged(sender: self)
}
}
class DefineViewController: UIViewController {
var delegate:DefineVCDelegate! = nil
var someData:Any!
func dismissMe() {
delegate.somethingChanged(sender: self)
dismiss(animated: true, completion: nil)
}
}
}
Basically, you are making VC#1 be a delegate to VC2. I prefer the declaration syntax in VC#2 for `delegate because if you forget to set VC#1 to be a delegate for VC#2, you test will force an error at runtime.

Open different new view controllers by clicking different elements in table view cell - Swift 3

My table view cell displays an entity with two different button elements. I want to be able to launch a view controller that displays a selection of food items if I click on the first button and a different view controller that displays a selection of beverages when I click on the second button.
I am able to correctly pass the data to the new view controllers, but can't seem to dismiss the current view and load the new one. My code is like this:
In the table view cell
#IBAction func foodBtnPressed(_ sender: Any) {
print("foodBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Food", forCustomer: customer!)
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
print("beverageBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Beverage", forCustomer: customer!)
}
In the table view controller
func loadChooserScreen(toChoose: String, forCustomer: Customer) {
print("Choose \(toChoose)")
print("For \(forCustomer.name)")
if toChoose == "Food" {
let foodVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "foodMenu") as? FoodVC
foodVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else if toChoose == "Beverage" {
let beverageVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "beverageMenu") as? BeverageVC
beverageVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else {
// do nothing
}
}
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
dismiss(animated: true, completion: {
self.delegate!.dismissViewController()
})
}
In this view controller I also have the following protocol
protocol OrderVCProtocol {
func dismissViewController()
}
and have defined
var delegate: OrderVCProtocol!
In my root view controller
func dismissViewController() {
print("Reached dismissViewController function in rootView")
if let foodVC = self.storyboard?.instantiateViewController(withIdentifier: "foodMenu") {
self.present(foodVC, animated: true, completion: nil)
}
if let beverageVC = self.storyboard?.instantiateViewController(withIdentifier: "beverageMenu") {
self.present(beverageVC, animated: true, completion: nil)
}
}
And the delegate is set when the table view controller is called here
#IBAction func loadOrderView(_ sender: Any) {
let orderVC = self.storyboard?.instantiateViewController(withIdentifier: "orderView") as! OrderVC
orderVC.delegate = self
self.present(orderVC, animated: true, completion: nil)
}
Within my target view controllers I have the following function
func loadCustomerToEdit(customer: Customer) {
self.customerToEdit = customer
print("IN FoodVC THE CUSTOMER TO EDIT IS \(self.customerToEdit.name)")
}
and a corresponding one in the BeverageVC.
When I run the app, no errors are thrown and I get the following sample output in the console from my print statements:
foodBtn pressed
customer is Optional("John")
Choose Food
For Optional("John")
IN FoodVC THE CUSTOMER TO EDIT IS Optional("John")
Reached dismissVC function in selectionMenu
and a corresponding response if the beverage button is clicked.
Then nothing happens. So I know the data is correctly being passed to the new view controllers but I don't know how to dismiss the current screen and display the new one with the choices.
I hope my question is clear enough? I'm not sure what's wrong, but the console output clearly shows that the code runs fine until it tries to dismiss the current view.
EDITED TO ADD:
If I modify my dismissVC function in my tableview controller like this:
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
delegate.dismissViewController()
}
the console view now throws
fatal error: unexpectedly found nil while unwrapping an Optional value
And if I modify it again to the following, It goes back to throwing no errors and getting stuck at the same place (i.e. printing the line "Stuck where delegate dismisses view"), showing that the delegate is still nil... but why is it nil when I'd set it in the root view and loaded it in this view?
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
if delegate != nil {
delegate?.dismissViewController()
} else {
print("Stuck where delegate dismisses view")
}
I have solved my problem by implementing notifications via notification centre and delegates. Firstly, in my AppDelegate file I added this line at the bottom
let notifyCnt = NotificationCenter.default
Next, I modified my tableview cell functions to this
#IBAction func foodBtnPressed(_ sender: Any) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Food", "forCustomer": customer])
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Beverage", "forCustomer": customer])
}
Then, in the tableview controller I modified it to this:
protocol ChooserViewDelegate: class {
func loadChooserView(choice: String, forCustomer: Customer)
}
and defined
weak var delegate: ChooserViewDelegate?
and added this within my ViewDidLoad section
notifyCnt.addObserver(forName: Notification.Name(rawValue: "toChoose"), object: nil, queue: nil, using: loadChooserScreen)
and finally modified my chooser function like so:
func loadChooserScreen(notification: Notification) {
guard let userInfo = notification.userInfo,
let toChoose = userInfo["toChoose"] as? String,
let planToEdit = userInfo["customer"] as? Customer else {
print("No userInfo found in notification")
return
}
delegate?.loadChooserView(choice: toChoose, forCustomer: customer)
}
Then in my root view controller I have the following to replace what I had earlier:
/*Conform to ChooserViewDelegate Protocol */
func loadChooserView(choice: String, forCustomer: Customer) {
self.customer = forCustomer
dismiss(animated: false, completion: {
if choice == "Food" {
self.performSegue(withIdentifier: "food", sender: self.customer)
}
if choice == "Beverage" {
self.performSegue(withIdentifier: "beverage", sender: self.customer)
}
})
}
and I send over the data via prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "food" {
if let foodVC = segue.destination as? FoodVC {
storyboard?.instantiateViewController(withIdentifier: "food")
foodVC.customerToEdit = self.customerToEdit
foodVC.delegate = self
}
}
if segue.identifier == "beverage" {
if let beverageVC = segue.destination as? BeverageVC {
storyboard?.instantiateViewController(withIdentifier: "beverage")
beverageVC.customerToEdit = self.customerToEdit
beverageVC.delegate = self
}
}
}
So now everything loads and views correctly :)

Swift - how to transfer from one view controller to another?

Back to my first swift app, I'm doing the sign in/sign up part, and basically the design with the view controllers so far are:
Welcome screen (sign in/sign up buttons) - Sign in - Sign up - Main Program
I got to the part where in the sign up view controllers, if the user misses one of the required field blanks and they click the button "submit", they will be prompted and back to the current view controller (sign up) to fill the missing fields. Now I want to set that when all the fields are filled, what line of code can I use so when they click the button "submit" it will head back to the welcome screen, so they can sign in afterwards?
Similarly, I got all the the part to check if the user enters correct user name and password, so when they entered the wrong info, they will be prompted and hack to the current view controller (sign in), and how can I do so if they entered the correct user name - password, it will head to the main program view controller (the 4th one)
If what I said above is confusing, I'll post my current controllers and the related code in here. Anyone has any idea? Again, this is my first program so it'd mean very much if I can get some helps from you. Thank you for reading.
An example on how to do it:
import UIKit
class WelcomeVC: UIViewController {
//make it IBAction or call it from IBAction
func singInButtonPressed(){
performSegueWithIdentifier("sign in", sender: self)
}
//make it IBAction or call it from IBAction
func singUPButtonPressed(){
performSegueWithIdentifier("sign up", sender: self)
}
}
class SignUPVC: UIViewController {
#IBOutlet var userNameField:UITextField!
#IBOutlet var passwordField:UITextField!
//make it IBAction or call it from IBAction
func submit(){
if checkValidity() {
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
func checkValidity() ->Bool{
if !userNameField.text!.isEmpty{
if !passwordField.text!.isEmpty {
return true
} else {
//do something to inform passwordfield is missing
}
} else{
//username field is missing
}
return false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sign in" {
if let sINvc = segue.destinationViewController.contentViewController as? SignInVC{
sINvc.username = userNameField.text
sINvc.password = passwordField.text
//and pass more of the other info you gathered in this vc
}
}
}
}
class SignInVC: UIViewController {
#IBOutlet var userNameField:UITextField!{
didSet {
if username != nil { userNameField.text = username }
}
}
#IBOutlet var passwordField:UITextField! {
didSet {
if password != nil { passwordField.text = password }
}
}
var password:String?
var username:String?
//make it IBAction or call it from IBAction
func signInPressed(){
if !userNameField.text!.isEmpty && !passwordField.text!.isEmpty {
performSegueWithIdentifier("Main page", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Main page"{
//your preparation
}
}
}
//this extension is helpful to avoid typing this everytime there is nav vc in your way of segueing
extension UIViewController{
var contentViewController: UIViewController {
if self is UINavigationController{
if let cvc = (self as! UINavigationController).visibleViewController { return cvc }
}
return self
}
}
simple way to transfer one view to another in swift is this given below:-
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//in "Main" your storyboard name
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("LoginPage") as! UIViewController
//in place of Login Page your storyboard identifier name
navigationController?.pushViewController(secondViewController, animated: true)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let mainTabBar = storyBoard.instantiateViewController(withIdentifier: "MainTabBar") as! UITabBarController
self.navigationController?.pushViewController(mainTabBar, animated: true)
works for me. thanks! update for the swift 3.1 and Xcode 8.3.2

Swift: Programmatically Navigate to ViewController and Pass Data

I recently started to learn swift and it has been pretty good so far. Currently I'm having an issue trying to pass data between view controllers. I managed to figure out how to programmatically navigate between two view controllers using a navigation controller. Only problem is now I'm having a hard time trying to figure out how to pass three string entered by the user (for json api) to the next view.
Here's my current attempt. Any help is much appreciated!
ViewController:
/* Get the status code of the connection attempt */
func connection(connection:NSURLConnection, didReceiveResponse response: NSURLResponse){
let status = (response as! NSHTTPURLResponse).statusCode
//println("status code is \(status)")
if(status == 200){
var next = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as! SecondViewController
self.presentViewController(next, animated: false, completion: nil)
}
else{
RKDropdownAlert.title("Error", message:"Please enter valid credentials.", backgroundColor:UIColor.redColor(), textColor:UIColor.whiteColor(), time:3)
drawErrorBorder(usernameField);
usernameField.text = "";
drawErrorBorder(passwordField);
passwordField.text = "";
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let navigationController = segue.destinationViewController as! UINavigationController
let newProjectVC = navigationController.topViewController as! SecondViewController
newProjectVC.ip = ipAddressField.text
newProjectVC.username = usernameField.text
newProjectVC.password = passwordField.text
}
SecondViewController:
import UIKit
class SecondViewController: UIViewController {
var ip:NSString!
var username:NSString!
var password:NSString!
override func viewDidLoad() {
super.viewDidLoad()
println("\(ip):\(username):\(password)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The method prepareForSegue is called when your app's storyboard performs a segue (a connection that you create in storyboards with Interface Builder). In the code above though you are presenting the controller yourself with presentViewController. In this case, prepareForSegue is not fired. You can do your setup right before presenting the controller:
let next = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as! SecondViewController
next.ip = ipAddressField.text
next.username = usernameField.text
next.password = passwordField.text
self.presentViewController(next, animated: false, completion: nil)
You can read more about segue here
Updated syntax for Swift 3:
let next = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
next.ip = ipAddressField.text
next.username = usernameField.text
next.password = passwordField.text
self.present(next, animated: true, completion: nil)

Resources