I have to create a struct to check if two UITextField are valid.
And my idea is this, is it a good way to create a struct?
struct Credentials{
func isCorrect() -> Bool {
guard let username = emailTF.text else {
return false
}
guard let password = passwordTF.text else {
return false
}
return true
}
but I have some question:
how can I pass the values of emailTextField and passwordTF inside the struct? with an init method?
and is it better have some var or let inside the struct or is also a good idea have only a method inside a struct?
thanks
You can creare a struct like this
struct Credentials {
let email: String
let password: String
init?(email:String?, password: String?) {
guard let email = email, password = password else { return nil }
self.email = email
self.password = password
}
var correct: Bool {
// do your check
guard email.isEmpty == false && password.isEmpty == false else { return false }
return true
}
}
As you can see correct is a computed property, non a function because it does't need any params.
Usage
let correct = Credentials(email: emailTF.text, password: passwordTF.text)?.correct == true
Structs work roughly the same as classes. They can have variables and unlike classes if there are variables that are not explicitly given values swift will automatically create an initializer. You would do something like this.
let myStruct = Credentials(emailTF: "something", passwordTF: "something")
print(myStruct.isCorrect())
The function would use the let and var things just like a class does.
However, saying all this I would recommend just putting this function straight into your class, rather than in a struct. Just pass in values as parameters.
Related
I am using iOS Swift, and I am trying to understand how to execute a method once the value of two variables have been set up (non-null value) once the requests have finished.
After reading some documentation, I have found out some concepts which are interesting. The first one would be didSet, which works as an observer.
I could call the method using this method by simply using didSet if I would require just one variable
didSet
var myVar: String 0 {
didSet {
print("Hello World.")
}
}
Nevertheless, I also need to wait for the second one myVar2, so it would not work.
I have also found DispatchQueue, which I could use to wait a second before calling the method (the requests that I am using are pretty fast)
DispatchQueue
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
print("Hello world")
})
but I consider that this solution is not efficient.
Is there anyway to combine these two variables or requests in order to call a method once they have finishing setting the value?
Update
I have tried to replicate David s answer, which I believe is correct but I get the following error on each \.
Type of expression is ambiguous without more context
I copy here my current code
var propertiesSet: [KeyPath<SearchViewController, Car>:Bool] = [\SearchViewController.firstCar:false, \SearchViewController.secondCar:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var firstCar: Car? {
didSet {
propertiesSet[\SearchViewController.firstCar] = true
}
}
var secondCar: Car? {
didSet {
propertiesSet[\SearchViewController.secondCar] = true
}
}
The variables are set individually, each one on its own request.
You could make your properties optional and check they both have values set before calling your function.
var varA: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
var varB: String? = nil {
didSet {
if varA != nil && varB != nil {
myFunc()
}
}
}
Or you can call your function on each didSet and use a guard condition at the start of your function to check that both of your properties have values, or bail out:
var varA: String? = nil {
didSet {
myFunc()
}
}
var varB: String? = nil {
didSet {
myFunc()
}
}
func myFunc() {
guard varA != nil && varB != nil else { return }
// your code
}
First, you should think very carefully about what your semantics are here. When you say "set," do you mean "assigned a value" or do you mean "assigned a non-nil value?" (I assume you mean the latter in this case.) You should ask yourself, what should happen if your method has already fired, and then another value is set? What if one of the properties has a value is set, then nil is set, then another value set? Should that fire the method 1, 2, or 3 times?
Whenever possible you should work to make these kinds of issues impossible by requiring that the values be set together, in an init rather than mutable properties, for example.
But obviously there are cases where this is necessary (UI is the most common).
If you're targeting iOS 13+, you should explore Combine for these kinds of problems. As one approach:
class Model: ObservableObject {
#Published var first: String?
#Published var second: String?
#Published var ready = false
private var observers: Set<AnyCancellable> = []
init() {
$first.combineLatest($second)
.map { $0 != nil && $1 != nil }
.assign(to: \.ready, on: self)
.store(in: &observers)
}
}
let model = Model()
var observers: Set<AnyCancellable> = []
model.$ready
.sink { if $0 { print("GO!") } }
.store(in: &observers)
model.first = "ready"
model.second = "set"
// prints "GO!"
Another approach is to separate the incidental state that includes optionals, from the actual object you're constructing, which does not.
// Possible parameters for Thing
struct Parameters {
var first: String?
var second: String?
}
// The thing you're actually constructing that requires all the parameters
struct Thing {
let first: String
let second: String
init?(parameters: Parameters) {
guard let first = parameters.first,
let second = parameters.second
else { return nil }
self.first = first
self.second = second
}
}
class TheUIElement {
// Any time the parameters change, try to make a Thing
var parameters: Parameters = Parameters() {
didSet {
thing = Thing(parameters: parameters)
}
}
// If you can make a Thing, then Go!
var thing: Thing? {
didSet {
if thing != nil { print("GO!") }
}
}
}
let element = TheUIElement()
element.parameters.first = "x"
element.parameters.second = "y"
// Prints "GO!"
You need to add a didSet to all variables that need to be set for your condition to pass. Also create a Dictionary containing KeyPaths to your variables that need to be set and a Bool representing whether they have been set already.
Then you can create a didSet on your Dictionary containing the "set-state" of your required variables and when all of their values are true meaning that all of them have been set, execute your code.
This solution scales well to any number of properties due to the use of a Dictionary rather than manually writing conditions like if aSet && bSet && cSet, which can get out of hand very easily.
class AllSet {
var propertiesSet: [KeyPath<AllSet, String>:Bool] = [\.myVar:false, \.myVar2:false] {
didSet {
if propertiesSet.allSatisfy({ $0.value }) {
// Conditions passed, execute your custom logic
print("All Set")
} else {
print("Not yet")
}
}
}
var myVar: String {
didSet {
propertiesSet[\.myVar] = true
}
}
var myVar2: String {
didSet {
propertiesSet[\.myVar2] = true
}
}
init(myVar: String, myVar2: String) {
self.myVar = myVar
self.myVar2 = myVar2
}
}
let all = AllSet(myVar: "1", myVar2: "2")
all.myVar = "2" // prints "Not yet"
all.myVar2 = "1" // prints "All set"
Apologies beforehand if this already has an answer but I wasn't able to find the answer that I was looking for.
I have been stuck on the best way to approach passing data from the UIView to the UIViewController. Let's suppose I have this form data with information the user has filled out. The data exists in the view via the individual UITextFields. How should I pass these information to the controller to perform validation and to create a post request with this data?
Does it make sense to do this via a closure? Like the following:
#objc func submitFormData() {
// call function passed via the view controller
}
What is the best practises for passing data between the view and the controller? For your information, I am not using StoryBoard and I am creating everything programmatically.
Answers would be greatly appreciated, thanks!
First, make a structure/Model of your data and do needfull validation.
class UserDataSpecifier {
var fields = [UserField]()
struct UserField {
var title: String?
var placeHolder: String = ""
var inputTxt: String = ""
var image: String = ""
init(titleStr: String, inputStr: String? = "", img: String = "") {
title = titleStr
placeHolder = titleStr
inputTxt = inputStr ?? ""
image = img
}
}
init() {
prepareForSignup()
}
func prepareForSignup() {
fields.append(UserField(titleStr: "First Name")
fields.append(UserField(titleStr: "Email"))
fields.append(UserField(titleStr: "Password"))
}
func isValidData(type: FormType) -> (isValid: Bool, error: String) {
if fields[0].inputTxt.isEmpty {
return (false, "Enter your message")
} else if fields[1].inputTxt.isEmpty {
return (false, "Enter your email")
}
return (true, "")
}
}
In your ViewController class make an instance of data. Fill the value of that object and then validate it.
var userData: UserDataSpecifier = UserDataSpecifier()
userData.fields[0].inputTxt = "name"
userData.fields[1].inputTxt = "email#gmail.com
let result = userData.isValidData()
if result.isValid {
print("Valid data")
} else {
print(result.error)
}
You can also pass this userData instance to your view and fill your data from your View. After filling data validate it in ViewController.
In my opinion the best way to pass data from the View to the ViewController would be via Delegates and Protocols.
Create a protocol in the ViewController with the submitFormData() function.
Declare a delegate variable in the view
Set the ViewController as the View's delegate
Then in your "submitFormData" function, call the delegate.submitFormData().
There are also other ways to pass data, this is just my personal preference. Hope this helped !
I went through the WWDC video of "Introducing Combine" where it was said that whenever a publisher value gets updated the CombineLatest gets called and updated. But the snippet I created works oddly.
class Mango {
var enableButton = false
#Published var userName = "admin"
#Published var password = "poweruser"
#Published var passwordAgain = "poweruser"
var validatePassword: AnyCancellable {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}
init() {
validatePassword
}
func checkSub() {
print("1. Is password same? ->",enableButton)
password = "nopoweruser"
print("2. Is password same? ->",enableButton)
}
}
When I initialize and call the function checkSub() where the publisher 'password' is updated the CombineLatest does not get called. Why is it behaving oddly?
Input:
let mango = Mango()<br>
mango.checkSub()
Output:
Is Password Same to poweruser? : true
In Map true
1. Is password same? -> true
2. Is password same? -> true
It seems like the issue is with memory management. The validatePassword cancellable is autoreleased, meaning that the subscription is completed as soon as you create it, since you do not retain it. Make it a property instead of computed property, using lazy var and it should work fine.
lazy var validatePassword: AnyCancellable = {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}()
With lazy you are retaining the cancellable which gets released only after the object is released. So, this should work properly.
I'm trying to learn mvc design pattern in swift. So I made the model class named User like below :
class User: NSObject {
var email : String!
var password : String!
var profilePictureUrl : String!
init(email: String, password: String, profilePictureUrl: String) {
super.init()
self.email = email
self.password = password
self.profilePictureUrl = profilePictureUrl
}}
and I'm using another class that store the function named loginConnection:
class loginConnection: NSObject {
class func loginUserWithEmailPassword(email: String,password: String) -> User{
return User(email: email, password: password, profilePictureUrl: "nil")
}}
And I try to set and get the email,password, and profilePictureUrl from my loginViewController but I always get nil when I print the User object.
var userObj : User!
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty != nil && userPasswordTextField.text?.isEmpty != nil{
loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
}
}
#IBAction func registerAction(sender: UIButton) {
print("\(userObj.email) >>>>> \(userObj.password)")
}
How can I access variable from User class?
Change your loginAction method as below,
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty == false && userPasswordTextField.text?.isEmpty == false {
self.userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
print("\(userObj.email) >>>>> \(userObj.password)")
}
}
1) you were comparing userEmailTextField.text?.isEmpty with nil, isEmpty returns Bool value.
2) you were not assigning value of type User returned by the function loginUserWithEmailPassword.
so then you have to do the following:-
var userObj : User = User()
userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
after that
userObj.email
Are you calling userObj from loginAction?
Like Below..
var userObj : User!
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty != nil && userPasswordTextField.text?.isEmpty != nil{
userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
print("\(userObj.email) >>>>> \(userObj.password)")
}
}
loginUserWithEmailPassword return User class object so you can use that to access User class properties
I can't see why this needs to be an NSObject. By making it a struct you can remove the init since it will come automatically. Also remove the ! in most cases since it quite dangerous to use implicitly unwrapped optionals unless you know what you're doing. Xcode will also help with the autocompletion and give good suggestions on how to fix as you go. If you do this you'll find the compiler tell you about the problems before run time errors can happen.
You declared userObj instance of User but you didn't assigned it by the values will return from loginUserWithEmailPassword function.
Assign you userObj as below in your viewController loginAction.
#IBAction func loginAction(sender: UIButton) {
userObj = loginConnection.loginUserWithEmailPassword("Karan", password:"karanPassword")
self.registerAction()
}
Now you will get the assigned username and password.
#IBAction func registerAction(sender: UIButton) {
print("\(userObj.email) >>>>> \(userObj.password)")
}
Here I got the username and password on my output window
With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.
1.
Why doesn't the code compile? The error message says:
All stored properties of a class instance must be initialized before returning nil from an initializer.
That doesn't make sense. Why should I initialize those properties when I plan to return nil?
2.
Is my approach the right one or would there be other ideas or common patterns to achieve my goal?
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
if let value: String = dictionary["user_name"] as? String {
userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
}
}
That doesn't make sense. Why should I initialize those properties when
I plan to return nil?
According to Chris Lattner this is a bug. Here is what he says:
This is an implementation limitation in the swift 1.1 compiler,
documented in the release notes. The compiler is currently unable to
destroy partially initialized classes in all cases, so it disallows
formation of a situation where it would have to. We consider this a
bug to be fixed in future releases, not a feature.
Source
EDIT:
So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
Update: From the Swift 2.2 Change Log (released March 21, 2016):
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
For Swift 2.1 and earlier:
According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Note: It actually works fine for structures and enumerations, just not classes.
The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.
Example from the docs:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
In the example above, the name property of the Product class is
defined as having an implicitly unwrapped optional string type
(String!). Because it is of an optional type, this means that the name
property has a default value of nil before it is assigned a specific
value during initialization. This default value of nil in turn means
that all of the properties introduced by the Product class have a
valid initial value. As a result, the failable initializer for Product
can trigger an initialization failure at the start of the initializer
if it is passed an empty string, before assigning a specific value to
the name property within the initializer.
In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.
class User: NSObject {
let userName: String!
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
super.init()
if let value = dictionary["user_name"] as? String {
self.userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
self.isSuperUser = value
}
self.someDetails = dictionary["some_details"] as? Array
}
}
I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.
class User: NSObject {
let userName: String = ""
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: [String: AnyObject]) {
if let user_name = dictionary["user_name"] as? String {
userName = user_name
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
if userName.isEmpty {
return nil
}
}
}
Another way to circumvent the limitation is to work with a class-functions to do the initialisation.
You might even want to move that function to an extension:
class User: NSObject {
let username: String
let isSuperUser: Bool
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
super.init()
}
}
extension User {
class func fromDictionary(dictionary: NSDictionary) -> User? {
if let username: String = dictionary["user_name"] as? String {
let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
let someDetails = dictionary["some_details"] as? [String]
return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
}
return nil
}
}
Using it would become:
if let user = User.fromDictionary(someDict) {
// Party hard
}
Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.
I found out this can be done in Swift 1.2
There are some conditions:
Required properties should be declared as implicitly unwrapped optionals
Assign a value to your required properties exactly once. This value may be nil.
Then call super.init() if your class is inheriting from another class.
After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.
Example:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
A failable initializer for a value type (that is, a structure or
enumeration) can trigger an initialization failure at any point within
its initializer implementation
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sg/jEUH0.l
You can use convenience init:
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
}
convenience init? (dict: NSDictionary) {
guard let userName = dictionary["user_name"] as? String else { return nil }
guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
guard let someDetails = dictionary["some_details"] as? [String] else { return nil }
self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
}
}