How to pass data from a view controller to a model? - swift

I am currently working on a project that has a Create Account page represented by a view controller called CreateNewAccount and a class of the same name which accompanies it. There are four values that are inputted into this view controller: 1) firstName, 2) lastName, 3) username, and 4) password. This view controller also has a "Create Account" button that when pressed, should transfer the String values inputted in the 4 inputs to a new class called UInfoRetrieveModel, which would be classified as a Model under the MVC configuration. Unfortunately this value transference part is not working.
I then have UInfoRetrieveModel pass these 4 values directly to another Model called UserInfo which then delegates out any of these values to other view controllers on the UI side that may need them displayed. I have figured out how to pass values from UInfoRetrieveModel to UserInfo and from UserInfo (which is a model) to said view controllers but I have not figured out how to pass from a view controller, specifically CreateNewAccount, to a model, which in this case is UInfoRetrieveModel.
Basically my idea here is to have two model classes: one model that receives (UInfoRetrieveModel) and one that delegates out (UserInfo) the data values set in CreateNewAccount, in order to make the transference of data across the UI more efficient.
Below is my code for CreateNewAccount and UInfoRetrieveModel, where the transference seems to not be working:
UInfoRetrieveModel->
import Foundation
protocol UInfoRetrieveModelDelegate: class {
func credentialTransfer(data:String)
}
class UInfoRetrieveModel: NSObject {
weak var delegate: UInfoRetrieveModelDelegate?
var firstName: String = ""
var lastName: String = ""
var userName: String = ""
var password: String = ""
func retrieving(){
delegate?.credentialTransfer(data: firstName)
delegate?.credentialTransfer(data: lastName)
delegate?.credentialTransfer(data: userName)
delegate?.credentialTransfer(data: password)
}
}
CreateNewAccount->
import UIKit
class CreateNewAccount: UIViewController{
#IBOutlet weak var FNInput: UITextField!
#IBOutlet weak var LNInput: UITextField!
#IBOutlet weak var usernameInput: UITextField!
#IBOutlet weak var passwordInput: UITextField!
var uInfoRetrieve = UInfoRetrieveModel()
#IBAction func thanksForJoining(_ sender: Any) {
uInfoRetrieve.firstName = FNInput.text!
uInfoRetrieve.lastName = LNInput.text!
uInfoRetrieve.userName = usernameInput.text!
uInfoRetrieve.password = passwordInput.text!
uInfoRetrieve.retrieving()
uInfoRetrieve.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension CreateNewAccount: UInfoRetrieveModelDelegate{
func credentialTransfer(data: String) {
print(data)
}
}

You are calling retrieving() in the wrong order. Use this order instead:
#IBAction func thanksForJoining(_ sender: Any) {
uInfoRetrieve.firstName = FNInput.text!
uInfoRetrieve.lastName = LNInput.text!
uInfoRetrieve.userName = usernameInput.text!
uInfoRetrieve.password = passwordInput.text!
uInfoRetrieve.delegate = self
uInfoRetrieve.retrieving()
}
Reason: If you call retreiving() before setting the delegate, the delegate will be nil and you won't get the callback.

Related

How do I check if x is in a variable in classes within a list?

I have a list called mainframe which holds classes. I want to check before adding a new username; if newusername is in mainframe.usernames perform adding the new username in.
pretty much something like this:
import UIKit
class addNewPassword: UIViewController {
var homeVC = Home()
#IBOutlet weak var createHolderItem: UITextField!
#IBOutlet weak var createHolderUsername: UITextField!
#IBOutlet weak var createHolderPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func savePasswordButton(_ sender: Any) {
let holder = Holder()
holder.item = createHolderItem.text!
holder.username = createHolderUsername.text!
holder.password = createHolderPassword.text!
}
if mainframe.contains(where: { $0.username == holder.username }) {
print("test")
}
else {
homeVC.mainframe.append(holder)
homeVC.tableView.reloadData()
navigationController?.popViewController(animated: true)
}
}
I pretty much want to run a loop, within an if statement. Or am I approaching it the wrong way?
I'm new to programming, did online tutorials and trying to write my first iOS app for my aunt.
if mainframe.usernames.contains(holder.username) {
...
Use contains :
if mainframe.usernames.contains(holder.username) {
...
}

How to pass data from one model to another in Swift?

I am working on a project containing a "Create New Account" view controller with its accompanying Swift class called "CreateNewAccount." The user can place 4 input values into this view controller, a first name, last name, user name, and password. Upon clicking the "Create Account" button in this VC, the 4 input values are passed on to a Swift class (within the model layer of MVC, I believe) called UserInfoRetrieveModel where they are supposedly stored.
I would then like to pass these values to another Swift class (that is a model as well) called UserInfoModel, which will then delegate out the first name value to the text value of label located in a VC called "ThanksForJoining" (and its accompanying class).
I have figured out how to pass values from VC to model (CreateNewAccount to UserInfoRetrieveModel) and from model to VC (UserInfoModel to ThanksForJoining), but somewhere in my transference from model to model (UserInfoRetrieveModel to UserInfoModel) the values initially inputted into "CreateNewAccount," which I would like to pass over to the second model class UserInfoModel become nil.
Below is the code for CreateNewAccount, UserInfoRetrieve, UserInfo, and ThanksForJoining:
CreateNewAccount ->
import UIKit
class CreateNewAccount: UIViewController{
#IBOutlet weak var FNInput: UITextField!
#IBOutlet weak var LNInput: UITextField!
#IBOutlet weak var usernameInput: UITextField!
#IBOutlet weak var passwordInput: UITextField!
var uInfoRetrieve = UInfoRetrieveModel()
#IBAction func thanksForJoining(_ sender: Any) {
uInfoRetrieve.firstName = FNInput.text!
uInfoRetrieve.lastName = LNInput.text!
uInfoRetrieve.userName = usernameInput.text!
uInfoRetrieve.password = passwordInput.text!
uInfoRetrieve.delegate = self
uInfoRetrieve.retrieving()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension CreateNewAccount: UInfoRetrieveModelDelegate{
func credentialTransfer(data: String) {
print(data)
}
}
UserInfoRetrieve ->
import Foundation
protocol UInfoRetrieveModelDelegate: class {
func credentialTransfer(data:String)
}
class UInfoRetrieveModel: NSObject {
weak var delegate: UInfoRetrieveModelDelegate?
var firstName: String = ""
var lastName: String = ""
var userName: String = ""
var password: String = ""
func retrieving(){
delegate?.credentialTransfer(data: firstName)
delegate?.credentialTransfer(data: lastName)
delegate?.credentialTransfer(data: userName)
delegate?.credentialTransfer(data: password)
}
}
UserInfo ->
import Foundation
protocol UserInfoModelDelegate: class {
func didReceiveDataUpdate(data: String)
}
class UserInfoModel {
weak var delegate: UserInfoModelDelegate?
let uInfoRetrieve = UInfoRetrieveModel()
func requestData() -> Array<String> {
let firstName = uInfoRetrieve.firstName
let lastName = uInfoRetrieve.lastName
let userName = uInfoRetrieve.userName
let password = uInfoRetrieve.password
delegate?.didReceiveDataUpdate(data: firstName)
delegate?.didReceiveDataUpdate(data: lastName)
delegate?.didReceiveDataUpdate(data: userName)
delegate?.didReceiveDataUpdate(data: password)
let credentials = [firstName, lastName, userName, password] as [Any]
return credentials as! Array<String>
}
}
ThanksForJoining ->
import UIKit
class ThanksForJoining: UIViewController {
var userInfo = UserInfoModel()
#IBOutlet weak var firstName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
userInfo.delegate = self
firstName.text = userInfo.requestData()[0]
print("yo")
print(userInfo.requestData()[0])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ThanksForJoining: UserInfoModelDelegate {
func didReceiveDataUpdate(data: String) {
print(data)
}
}
UserInfoModel and CreateNewAccount do both create a new instance of UInfoRetrieveModel. You have to connect them properly for them to pass on information.
Connecting properly means (in the simplest form) one constructs the other and sets itself as the delegate of the other, so UInfoRetrieveModel can pass on data. The constructing of a child model is usually done via a computed property.
Example
struct Account {
let firstName: String, lastName: String
let userName: String, password: String
}
extension UInfoRetrieveModelDelegate: class {
createAccount(_ account: Account): Bool
}
extension UserInfoModel: UInfoRetrieveModelDelegate{
func createAccount(_ account: Account) -> Bool {
// Handling creation of account.
return success == true
}
var newUInfoRetrieveModel: UInfoRetrieveModel {
let helperModel = UInfoRetrieveModel(parent: self)
helperModel.delegate = self
return helperModel
}
}
Explanation
Yes. You usually have a Model, your data, then have something that controls access to it to make changes on your model, manages how the model is stored, maybe syncing with a cloud-service, thats the ModelController which you pass around between ViewControllers, more/other controllers you usually use incase that makes things simpler. In your case you would probably pass createAccount(the call) on to a controller/viewController which is in charge of telling the modelController to create the account and then telling one of its views/viewControllers to display the modal/whatever.
The usual way to pass data to a higher level is to have for the viewController/controller a delegate it uses to communicate with higher up, the one “responsible for actions the ViewController/controller cannot do by itself”, eg pushing data up(creation calls, modification calls, deletion calls) if it makes no sense to give it a modelController since its not control of that part of the application, etc. In your case you can of course pass a modelController to each little viewController/view, but its usually more practical/simpler to only give it to the one controlling the part and let others communicate with that currently-that-part-controlling controller/viewController.
More partical here means that you may not want CreateAccountViewController to display the success dialog, but rather another, which CreateAccountViewController can then do not by itself since it’s not on the stack anymore.

Swift MVC architecture with model observer

Using the MVC approach for iOS app development, I would like to observe changes to the model by posting to the NotificationCenter. For my example, the Person.swift model is:
class Person {
static let nameDidChange = Notification.Name("nameDidChange")
var name: String {
didSet {
NotificationCenter.default.post(name: Person.nameDidChange, object: self)
}
}
var age: Int
var gender: String
init(name: String, age: Int, gender: String) {
self.name = name
self.age = age
self.gender = gender
}
}
The view controller that observes the model is shown below:
class ViewController: UIViewController {
let person = Person(name: "Homer", age: 44, gender: "male")
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var ageLabel: UILabel!
#IBOutlet weak var genderLabel: UILabel!
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var ageField: UITextField!
#IBOutlet weak var genderField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = person.name
ageLabel.text = String(person.age)
genderLabel.text = person.gender
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateLabels),
name: Person.nameDidChange, object: nil)
}
#IBAction func updatePerson(_ sender: Any) {
guard let name = nameField.text, let age = ageField.text, let gender = genderField.text else { return }
guard let ageNumber = Int(age) else { return }
person.name = name
person.age = ageNumber
person.gender = gender
}
#objc func updateLabels() {
nameLabel.text = person.name
ageLabel.text = String(person.age)
genderLabel.text = person.gender
}
}
The example app works as follows:
Enter a name, age, and gender in the text fields
Press the updatePerson button to update the model from the text field values
When the model is updated, the notification observer calls the updateLabels function to update the user interface.
This approach requires the person.name to be set last otherwise the updatePerson button must be pressed twice to update the entire user interface. And since I'm only observing one property, the notification does not represent the entire class. Is there a better way to observe changes of models (a class or struct) in Swift?
Note - I am not interested in using RxSwift for this example.
This is more of a dumping comment than a fulfilling answer. But long story short KVO is the feature you should be using, not NotificationCenter. The binding process becomes significantly more simple in Swift4
As for what KVO is: See here and here. For some examples which are MVVM focused you can see here and here. And don't let the MVVM sway you away. It's just MVC with bindings which you are trying to do the exact same thing + moving the presentation logic to a different layer.
A simple KVO example in Swift 4 would look like this:
#objcMembers class Foo: NSObject {
dynamic var string: String
override init() {
string = "hotdog"
super.init()
}
}
let foo = Foo()
// Here it is, kvo in 2 lines of code!
let observation = foo.observe(\.string) { (foo, change) in
print("new foo.string: \(foo.string)")
}
foo.string = "not hotdog"
// new foo.string: not hotdog
You can also create your own Observable type like below:
class Observable<ObservedType>{
private var _value: ObservedType?
init(value: ObservedType) {
_value = value
}
var valueChanged : ((ObservedType?) -> ())?
public var value: ObservedType? {
get{
return _value // The trick is that the public value is reading from the private value...
}
set{
_value = newValue
valueChanged?(_value)
}
}
func bindingChanged(to newValue : ObservedType){
_value = newValue
print("value is now \(newValue)")
}
}
Then to create an observable property you'd do:
class User {
// var name : String <-- you normally do this, but not when you're creating as such
var name : Observable<String>
init (name: Observable<String>){
self.name = name
}
}
The class above (Observable) is copied and pasted from Swift Designs patterns book
To simply visualize the picture, you should be aware of the fact that you are observing only the name change. So it doesn't make sense to update all of the other properties of Person. You are observing name change and it's being updated accordingly, let alone others.
So it's not an ideal assumption that age and gender might have been changed in the process of changing name. Being said that, you should consider observing all of the properties one by one and bind actions differently and modify only the UI component that is mapped to that specific property.
Something like this:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateName),
name: Person.nameDidChange, object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateAge),
name: Person.ageDidChange, object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateGender),
name: Person.genderDidChange, object: nil)
...
}
#objc func updateName() {
nameLabel.text = person.name
}
#objc func updateAge() {
ageLabel.text = String(person.age)
}
#objc func updateGender() {
genderLabel.text = person.gender
}

Initializing class object swift 3

i have a problem while I'm initializing object of some class. What is wrong with that? (I can upload all my code but it is large if needed)
Edit:
My view controller code:
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var answerStackView: UIStackView!
// Feedback screen
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var dimView: UIView!
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var feedbackLabel: UILabel!
#IBOutlet weak var resultButton: UIButton!
#IBOutlet weak var resultViewBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var resultViewTopConstraint: NSLayoutConstraint!
var currentQuestion:Question?
let model = QuizModel()
var questions = [Question]()
var numberCorrect = 0
override func viewDidLoad() {
super.viewDidLoad()
model.getQuestions()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setAll(questionsReturned:[Question]) {
/*
// Do any additional setup after loading the view, typically from a nib.
// Hide feedback screen
dimView.alpha = 0
// Call get questions
questions = questionsReturned
// Check if there are questions
if questions.count > 0 {
currentQuestion = questions[0]
// Load state
loadState()
// Display the current question
displayCurrentQuestion()
}
*/
print("Called!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
My QuizModel code:
import UIKit
import FirebaseDatabase
class QuizModel: NSObject {
override init() {
super.init()
}
var ref:FIRDatabaseReference?
var test = [[String:Any]]()
var questions = [Question]()
weak var prot:UIPageViewControllerDelegate?
var first = ViewController()
func getQuestions(){
getRemoteJsonFile()
}
func pars(){
/*let array = test
var questions = [Question]()
// Parse dictionaries into Question objects
for dict in array {
// Create question object
let q = Question()
// Assign question properties
q.questionText = dict["question"] as! String
q.answers = dict["answers"] as! [String]
q.correctAnswerIndex = dict["correctIndex"] as! Int
q.module = dict["module"] as! Int
q.lesson = dict["lesson"] as! Int
q.feedback = dict["feedback"] as! String
// Add the question object into the array
questions += [q]
}
*/
//Protocol setAll function
first.setAll(questionsReturned: questions)
}
func getRemoteJsonFile(){
ref = FIRDatabase.database().reference()
ref?.child("Quiz").observeSingleEvent(of: .value, with: { (snapchot) in
print("hey")
let value = snapchot.value as? [[String:Any]]
if let dict = value {
self.test = dict
self.pars()
}
})
}
This isn't my all code but I think that is the most important part. In QuizModel code I'm reskining my code from getting json file to get data from firebase so you can see names of functions like 'getRemoteJSONFile' and in parse function parsing json but it isn't an issue of my problem I think
It looks like you're trying to initialize a constant before you've initialized the viewController it lives in. Your have to make it a var and initialize it in viewDidLoad (or another lifecycle method), or make it a lazy var and initialize it at the time that it's first accessed.
The problem boils down to the following:
class ViewController ... {
let model = QuizModel()
}
class QuizModel ... {
var first = ViewController()
}
The variable initializers are called during object initialization. When you create an instance of ViewController, an instance of QuizModel is created but its initialization creates a ViewController which again creates a new QuizModel and so on.
This infinite cycle will sooner or later crash your application (a so called "stack overflow").
There is probably some mistake on your side. Maybe you want to pass the initial controller to QuizModel, e.g.?
class ViewController ... {
lazy var model: QuizModel = {
return QuizModel(viewController: self)
}
}
class QuizModel ... {
weak var viewController: ViewController?
override init(viewController: ViewController) {
self.viewController = viewController
}
}
Also make sure you are not creating ownership cycles (that's why I have used weak).
In general it's a good idea to strictly separate your model classes and your UI classes. You shouldn't pass view controllers (or page view controller delegate) to your model class. If you need to communicate with your controller, create a dedicated delegate for that.

Swift: access Variables from other classes in different swift files

I have tried to implement the solution from this answer but I just haven't been able to get it to work. I get 'Use of unresolved identifier...' for the variables I am trying to access..
I have a loginViewController.swift that gets the username and password when someone logs in..
import UIKit
class loginViewController: UIViewController {
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func loginButtonTapped(sender: AnyObject) {
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
Then I have a PostService.swift file that gets data from a MySQL database.. It should use the username of the person logged in to access the relevant data..
import Foundation
class PostService {
var settings:Settings!
let userEmail = "steve#centrix.co.za"; //This is hard coded but needs to come from the loginViewController
let userPassword = "1234"; //This is hard coded but needs to come from the loginViewController
I have tried that and a few other suggestions and I just don't seem to be able to get it to work.. Please help..
Thanks in advance.
You can just declare
var userEmail = "";
var userPassword = "";
outside a class (it doesn't matter which Swift class). Then, dropping the let,
#IBAction func loginButtonTapped(sender: AnyObject) {
userEmail = userEmailTextField.text;
userPassword = userPasswordTextField.text;
will store the variables, and you'll just be able to use the in the PostService class.
However, is there really no connection between the LoginViewController and the PostService? You'll have to call a PostService function somewhere ... maybe you can pass the variables there?