I have a user class defined in UserClass.swift like this:
class User {
var id:Int
var name:String
var phone:String
var email:String
init(id:Int, name:String, phone:String, email:String){
self.id = id
self.name = name
self.phone = phone
self.email = email
}
}
In my ProfileViewController I call a method on viewDidLoad called getUserData.
class ProfileViewController: UIViewController {
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
getUserData { myUser in
self.userNameLabel.text = myUser.name
}
}
getUserData pings my API and then instantiates and instance of the User class.
func getUserData(completionHandler:(User)->()) {
Alamofire.request(.GET, "apiEndPoint")
.responseJSON { response in
if let value: AnyObject = response.result.value {
let userData = JSON(value)
let userName = userData["name"].string!
let userId = userData["id"].int!
let userPhone = userData["phone"].string!
let userEmail = userData["email"].string!
let myUser = User(id:userId, name:userName, phone:userPhone, email:userEmail)
print("user: \(myUser.name)")
completionHandler(myUser)
}
}
}
I then pass that instance with a callback to the ProfileViewController so that I can update a label. This works great.
The problem is that now I want to look at a different property of the User instance myUser in a different place, outside of the callback. Eventually I want to get the user's id, but for now I'm just trying to access the instance at all, so I'm looking again at updating a label with the user's name:
class MyActivitiesViewController: UIViewController {
#IBOutlet weak var myActTempLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.myActTempLabel.text = myUser.name
}
}
This is giving me an error of Use of unresolved identifier 'myUser'. How do I access the instance of User that I already created?
You should keep a reference to the User object in a singleton in order to access it every where. Basically in cases like this you have a singleton class called say CurrentUser
class CurrentUser {
static var sharedInstance = CurrentUser()
var user:User?
}
Then in your network call or perhaps in the completion handler of the first view controller you where you know the first time the user is obtained you can set the User instance to the CurrentUser singleton.
CurrentUser.sharedInstance.user = myUser
Later you can use CurrentUser.sharedInstance.user in all the places where you want to get the user details.
it's very simple . just create a ref of User in your class and assign myUser to it
like:
class ProfileViewController: UIViewController {
var user: User?
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
getUserData { myUser in
self.user = myUser
self.userNameLabel.text = myUser.name
}
}
your instance myUser is just existed in your block. You need to create a variable to store this
class ProfileViewController: UIViewController {
var user:User?
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
getUserData { myUser in
self.userNameLabel.text = myUser.name
user = myUser
}
}
now you can use "user" property to access outside your block
Related
I have viewModel:
class EditFoodViewViewModel {
private var food: Food
var foodImage = Variable<NSData>(NSData())
init(food: Food) {
self.food = food
self.foodImage.value = food.image!
}
}
And ViewController:
class EditFoodViewController: UIViewController {
public var food: EditFoodViewViewModelType?
#IBOutlet weak var foodThumbnailImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
guard let foodViewModel = food else { return }
foodViewModel.foodImage.asObservable().bind(to: foodThumbnailImageView.rx.image).disposed(by: disposeBag)
}
}
In the last line of viewController (where my UIImageView) a get error:
Generic parameter 'Self' could not be inferred
How to solve my problem? How to set image to imageView with rxSwift?
Almost invariably, when you see the error: "Generic parameter 'Self' could not be inferred", it means that the types are wrong. In this case you are trying to bind an Observable<NSData> to an Observable<Image?>.
There's a few other issues with your code as well.
it is very rare that a Subject type should be defined with the var keyword and this is not one of those rare times. Your foodImage should be a let not a var.
Variable has been deprecated; don't use it. In this case, you don't even need a subject at all.
NSData is also inappropriate in modern Swift. Use Data instead.
Based on what you have shown here, I would expect your code to look more like this:
class EditFoodViewViewModel: EditFoodViewViewModelType {
let foodImage: Observable<UIImage?>
init(food: Food) {
self.foodImage = Observable.just(UIImage(data: food.image))
}
}
class EditFoodViewController: UIViewController {
#IBOutlet weak var foodThumbnailImageView: UIImageView!
public var food: EditFoodViewViewModelType?
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
guard let foodViewModel = food else { return }
foodViewModel.foodImage
.bind(to: foodThumbnailImageView.rx.image)
.disposed(by: disposeBag)
}
}
I am trying to implement MVVM Architecture pattern using Boxing. I have done it simply by Adding the Boxing Class:
class Dynamic<T> {
typealias Listener = (T) -> Void
var listener: Listener?
func bind(listener: Listener?) {
self.listener = listener
}
func bindAndFire(listener: Listener?) {
self.listener = listener
listener?(value)
}
var value: T {
didSet {
listener?(value)
}
}
init(_ v: T) {
value = v
}}
And then In the ViewController I have referenced a ViewModel, this is my View Controller:
class SignUpViewController: UIViewController {
// UI Outlets
#IBOutlet weak var emailLoginTextField: FloatLabelTextField!
#IBOutlet weak var passwordLoginTextField: FloatLabelTextField!
var viewModel = AuthenticationViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.user.email.bind{
self.emailLoginTextField.text = $0
}
}}
And This is my View Model:
class AuthenticationViewModel{
let defaults = UserDefaults.standard
let serviceManager = ServiceManager()
var user = User()
func signupUser(email : String?, password: String?){
let parameters : [String:String] = ["email":emailField, "password": password!, "system": "ios"]
serviceManager.initWithPOSTConnection(server: Utitlites.getServerName(), parameters: parameters, methodName: "/api/user/register", completion: { (responseData , errorMessage) -> Void in
let json = (responseData as AnyObject) as! JSON
print(json)
if ErrorHandling.handleErrorMessage(responseData: responseData).0 == true {
self.defaults.set("userId", forKey: json["user"]["id"].stringValue)
//self.userId.value = json["user"]["id"].stringValue
self.user = User(json: json)
}
})
}}
And this is my Model:
class User{
var id = Dynamic("")
var name = Dynamic("")
var email = Dynamic("")
init(){
}
init(json: JSON){
id.value = json["user"]["id"].stringValue
email.value = json["user"]["email"].stringValue
}}
My Question is:
MVVM Architecture wise, is it right to access the model using this line in the ViewController:
viewModel.user.email.bind{
self.emailLoginTextField.text = $0
}
Because I can see now that the View is accessing the Model which I think is not what MVVM Stands for. I need someone to clarify
The best practice to go about this (imo) and according to this raywanderlich video at 31:18 is to actually set the Model to be private, your VC doesn't need to know about it at all, only the ViewModel.
After that, set getters for the Model in the ViewModel like this:
var id: Dynamic<String> = Dynamic("")
var name: Dynamic<String> = Dynamic("")
var email: Dynamic<String> = Dynamic("")
And then, in your ViewModel also, set the User object to have a didSet notifier that will update the ViewModel's data accordingly:
private var user = User() {
didSet {
id = user.id
name = user.name
email = user.email
}
}
Now, you can access these properties only from the ViewModel instead of the Model directly:
viewModel.email.bind{
self.emailLoginTextField.text = $0
}
Oh, and don't forget to set the properties on the Model to be just regular strings ;)
I'm currently doing a login system test, I'd like to log in the accountTextfield of the string stored in the Account I built the class, so I can use in other controllers, this method should be how to achieve, can anyone help me, thank you
Here is class of Account
class Account {
var id:String?
init(id:String)
self.id = id
}
And here is my LoginViewController
#IBOutlet weak var accountTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBAction func DoLogin(_ sender: AnyObject) {
login_now(username:accountTextField.text!,password:passwordTextField.text!)
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
let url = URL(string: "http://163.18.22.78/api/Login")!
.
.
.
}
class Account: NSObject {
var id:String?
}
class Apimanager: NSObject {
static let instance = Apimanager()
var user :Account = Account()
}
func login_now(username:String, password:String)
{
let parameters = ["account": accountTextField.text!, "password": passwordTextField.text!] as Dictionary<String, String>
Apimanager.instance.user.id = accountTextField.text!
print(Apimanager.instance.user.id!)
}
In another view controller
Print(Apimanager.instance.user.id!
if you want use init() method in Account class then you can use
UserDefaults.standard.set(accountTextField.text!, forKey: "account")
UserDefaults.standard.synchronize()
to store particular value and to get value in another view controller you can use
print(UserDefaults.standard.value(forKey: "account"))..
note that both key value should be match for setting and getting value from UserDefaults..hope it work for you!!
I have a VC that contains of textField and button. I have a simple task to pass textField text data to my model entity. However, while I want to assign textField data to entity data, my data remains nil.
I can't understand what have caused such problem
class InputTextViewController: UIViewController {
#IBOutlet weak var messageTextField: UITextField!
//I receive user from parent VC by segue
var userSendMessageTo: User!
var tableView: UITableView?
var message: Message { return Message(userSendMessageTo) }
// initialising object of my entity class
override func viewDidLoad() {
super.viewDidLoad()
print(userSendMessageTo.name)
//name is right
}
func createMessage() {
message.messageText = messageTextField.text
message.messageTime = Date()
print(message.messageText) //nil
print(message.messageTime) //nil
}
#IBAction func sendMessge(_ sender: Any) {
createMessage()
userSendMessageTo.mesaageHistory.append(message)
print(userSendMessageTo.mesaageHistory[0].messageText) //nil
}
My model
class Message {
var messageText: String?
var messageTime: Date?
var messageInage: UIImage?
var user: User
init(_ user: User) {
self.user = user
}
}
class User {
let name: String
var mesaageHistory = [Message]()
init(name: String) {
self.name = name
}
}
The construct var message: Message { return Message(userSendMessageTo) } returns a new Message object every time it's called.
Therefore you are creating a message object, assigning text to it, then throwing it away. Then you create a second message object, assign a date to it and throw it away. Then you create a third message object and check its text (which is nil) and then you create a fourth message object and check its date, which is also nil.
Try this instead:
class InputTextViewController: UIViewController {
#IBOutlet weak var messageTextField: UITextField!
var userSendMessageTo: User!
var tableView: UITableView?
var message: Message?
func createMessage() {
message = Message()
message!.messageText = messageTextField.text
message!.messageTime = Date()
print(message!.messageText) //won't be nil
print(message!.messageTime) //won't be nil
}
}
You have declared message as a computed variable. This means that each time you reference message you are actually executing the following block of code:
Message(userSendMessageTo)
In other words, each reference to message creates a new instance of Message.
message.messageText = messageTextField.text - Creates a new instance of Message
message.messageTime = Date() - creates a new instance of Message
print(message.messageTime) - create a new instance of Message, and its messageTime property is nil
There is no need to use a property here; your createMessage function should return the new Message:
func createMessage(withText text: text) -> Message {
let message = Message(userSendMessageTo)
message.messageText = text
message.messageTime = Date()
return message
}
#IBAction func sendMessge(_ sender: Any) {
let message = self.createMessage(withText: messageTextField.text)
userSendMessageTo.mesaageHistory.append(message)
print(userSendMessageTo.mesaageHistory[0].messageText)
}
To be honest, if you create a proper initialiser for Message you could get rid of the createMessage function altogether:
class Message {
var messageText: String
var messageTime: Date
var messageInage: UIImage?
var user: User
class Message {
var messageText: String
var messageTime: Date
var messageInage: UIImage?
var user: User
init(_ user: User, text: String = "", date: Date = Date()) {
self.user = user
self.messageTime = date
self.messageText = text
}
}
Then your action method simply becomes:
#IBAction func sendMessge(_ sender: Any) {
let message = Message(userSendMessageTo, text: messageTextField.text, date:Date())
userSendMessageTo.mesaageHistory.append(message)
print(userSendMessageTo.mesaageHistory[0].messageText)
}
I tried creating global variables and updating the information when the view is loaded but data isn't being rendered.
GLOBAL VARIABLES
var viewName:String = ""
var viewDuration:String = ""
var viewPeriod:String = ""
var viewMinAmp:String = ""
var viewMaxAmp:String = ""
var viewStep:String = ""
var viewType:String = ""
Is there a more efficient way of passing information other than having global variables?
#IBOutlet var txtName: UITextField!
#IBOutlet var txtDuration: UITextField!
#IBOutlet var txtPeriod: UITextField!
#IBOutlet var txtMinAmp: UITextField!
#IBOutlet var txtMaxAmp: UITextField!
#IBOutlet var txtStep: UITextField!
#IBOutlet var txtType: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setInfo(viewName, duration: viewDuration, period: viewPeriod, minAmp: viewMinAmp, maxAmp: viewMaxAmp, step: viewStep, type: viewType)
}
func setInfo(name: String, duration: String, period: String, minAmp: String, maxAmp: String, step: String, type: String) {
txtName.text = name
txtDuration.text = duration
txtPeriod.text = period
txtMinAmp.text = minAmp
txtMaxAmp.text = maxAmp
txtStep.text = step
txtType.text = type
}
One solution would be to override prepareForSegue(segue:sender:) from within the view controller which contains the data that you wish to pass to the destination view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "YourSegueName") {
//get a reference to the destination view controller
let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass
//set properties on the destination view controller
destinationVC.name = viewName
//etc...
}
}
For Swift 3.0
final class Shared {
static let shared = Shared() //lazy init, and it only runs once
var stringValue : String!
var boolValue : Bool!
}
To set stringValue
Shared.shared.stringValue = "Hi there"
to get stringValue
if let value = Shared.shared.stringValue {
print(value)
}
For Swift version below 3.0
You can pass data between views using singleton class. It is easy and efficient way. Here is my class ShareData.swift
import Foundation
class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
Now in my ViewControllerOne I can set above variable.
//Declare Class Variable
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
self.shareData.someString ="Some String Value"
}
And in my ViewControllerTwo I can access someString as
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
NSLog(self.sharedData.someString) // It will print Some String Value
}
Personally, I prefer ways as follow:
If you want to jump forward between two view controllers (from A to B), as -pushViewController:animated: in navigation, you can define a property of model for Controller B and expose it publicly, then set this property explicitly before jumping from Controller A, it's pretty straightforward;
In case you want to jump backward from Controller B to A, use Delegate+Protocol mode. Controller B drafts a public protocol and own a "delegate" property, any object who would like to be the delegate of Controller B shall comply and implement its protocol(optionally). then prior to the jumping-backward, Controller B makes its delegate perform certain action(s) listed in protocol, the data could be transferred in this way;
Under certain circumstance, you may want to transfer data from a Controller(or controllers) to other multiple Controllers, use Notification mechanism if this is the case.
Apple has detailed instructions about delegate mode, notification mode in official documentation, check them out in XCode, :)
Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
create a segue between ViewControllerA and ViewControllerB
name the segue with a Identifier in the attributes inspector of it
override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
For step#3,:
if you are not using swift 2.1, please follow #Scott Mielcarski 's answer at this question
for people who are using swift 2.1, or who get error "Cannot convert value of type 'UIViewController' to specified type 'your view Controller class name', After following #Scott Mielcarski 's answer at this question, Please use:let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass instead of
let destinationVC:ViewControllerClass = segue.destinationViewController
This is tested on Swift 2.1.1 and it works for me.
If you don't actually want to pass data between view controllers but rather simply want to store a global variable you can do this:
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")