Boolean returns nil and unable to access value from response on view controller - swift

I have a usermodel that checks the backend if the email exists - then I drill back into a viewcontroller and set a boolean value that should trigger a function run. However the value is unchanged and I am trying to change this value from the usermodel but it is not accessible. I understand why it does not work.. but do not know how to resolve the issue.
static func sendEmailWithResetLink(email: String) {
let params : Parameters = [
PARAM_EMAIL : email
]
request(URL_RESET_PASSWORD as String, method: .post, parameters: params, headers: nil).responseJSON {
(response: DataResponse<Any>) in
hideProgress()
print("this is response \(response)")
switch(response.result)
{
case .success(_):
print("it did not fail")
let passwordResetVC = PasswordResetViewController()
passwordResetVC.hasFailed = false
break
case .failure(_):
print("it failed")
let passwordResetVC = PasswordResetViewController()
//here boolean is set that I am trying to access in viewcontroller
passwordResetVC.hasFailed = true
break
}
}
}

Here's what I would suggest. You probably have some of these in place already:
Create an PasswordResetViewController object has an #IBAction func resetButtonClicked triggered by a button or whatever, which kicks off the password reset process.
Create a UserManager class. This class is responsible for all profile management activies in your app. Among other things, it has the ability to reset user passwords. This UserManager would probably be a singleton, that' sprobably good enough for now.
Create a new UserManagerDelegate protocol. Add to it all capabilities that are required by the UserManager to inform them of whatever happened. For example: var passwordResetHasFailed: Bool { get set }.
Extend your PasswordResetViewController conform to this protocol.
Your VC gets a reference to the singleton UserManager object, stores it in an instance variable, and uses that to access the shared object from then on.
Make your PasswordResetViewController register itself as the delegate to the user manager, with userManager.delegate = self
The #IBAction func resetButtonClicked will just call userManager.resetPassword()
Your UserManager does whatever it needs to do to reset the user's password.
When it's done, it'll call self.delegate?.passwordResetHasFailed = true/false.
Since your PasswordResetViewController registered itself as the delegate of the UserManager, when the operation is done, its passwordResetHasFailed property will be changed, giving it a chance to respond (by updating some UI or whatever).
There are some limitations to this approach, but it's a decent way to get started. Some thing to note:
This lets you unit test your PasswordResetViewController. You can create a MockUserManager, and set tesPasswordResetViewController.userManager = MockUserManager(), allowing you to separate out the user manager, and test PasswordResetViewController in isolation.
You'll run into issues if you need multiple objects to subscribe to receive delegate call backs (since there can only be 1 delegate object). At that point, you can switch to using something like Promises, RxSwift or Combine. But that's a problem for a later time, and the migration would be easy.

Going off of #Alexander - Reinstate Monica and what I assume what the code to look like to approach your problem.
Using MVC:
In Models folder (data/ logic part)
public class User {
private var name: String!
private var userEmail: String!
public var hasFailed: Bool?
init() {
name = ""
userEmail = ""
hasFailed = nil
}
public func setName(name: String) { self.name = name }
public func getName() -> String { return name }
public func setEmail(email: String) { userEmail = email }
public func getEmail() ->String { return userEmail }
public static func sendEmailWithRestLing(email: String) {
// your other code
switch response.result {
case .success(_):
//your code
hasFailed = false
break
case .failuare(_):
// your code
hasFailed = true
break
}
}
}
User Manager class applying singleton design
final class UserManager {
private var user = User()
static let instance = UserManager()
private init(){}
public func userName(name: String) {
if (name.count > 3) {
user.setName(name: name)
}
else { print("user name is too short") }
}
public func userEmail(email: String) {
if (email.count > 3) {
user.setEmail(email: email)
}
else { print("user email is too short") }
}
public func getUserName() -> String {
let name = user.getName()
if (name.isEmpty) { return "user name is Empty" }
return name
}
public func getUserEmail() -> String {
let email = user.getEmail()
if (email.isEmpty) { return "user email is Empty" }
return email
}
public func doKatieTask(link: String) -> Int {
guard let myValue = user.hasFailed else {
return -1
}
if (myValue) { return 1}
return 0
}
}
So, Now in the Controllers folder and since we a one-to-one relation we will use delegate design pattern. If had had one-to-many with the view controller. Use observers.
class ViewController: UIViewController {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
var _hasFail: Bool!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func doTask() {
UserManager.instance.userName(name: nameTextField.text!)
UserManager.instance.userEmail(email: emailTextField.text!)
switch UserManager.instance.doKatieTask(link: emailTextField.text!) {
case 0:
_hasFail = false
break
case 1:
_hasFail = true
break
default:
print("hasFailed is nil")
break
}
if let vc = storyboard?.instantiateViewController(identifier: "passwordVC") as? PasswordResetViewController {
vc.modalPresentationStyle = .fullScreen
vc.delegate = self
self.present(vc, animated: true, completion: nil)
}
}
}
extension ViewController: KatieDelegate {
var hasFailed: Bool {
get {
return _hasFail
}
set {
_hasFail = newValue
}
}
}
In PasswordReset UIViewController
protocol KatieDelegate {
var hasFailed: Bool { get set }
}
class PasswordResetViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
var delegate: KatieDelegate?
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = UserManger.instance.getUserName()
emailLabel.text = UserManger.instance.getUserEmail()
if let delegate = delegate {
print("The value for has failed is: .....\(delegate.hasFailed)!")
}
else { print("error with delegate") }
}
}

Related

UnitTest does't work properly for MVP pattern Swift

I'm trying to write some UnitTests for the first time. My pattern is MVP and I'm trying to test my Presenter. I've created mock class: class TeamViewMock: TeamViewPresenterProtocol { }. It contains all the methods from my real Presenter. Inside the each method I'm trying to set the new value for the property, so when the method called - property should get a new value.
Only one property gets new value out of 4 and I've no clue why the other ones didn't get it.
You may see it in the following code
import XCTest
#testable import NHL
class TeamViewPresenterTest: XCTestCase {
var presenter: TeamViewPresenter!
var viewMock: TeamViewMock!
func setupPresenter() {
viewMock = TeamViewMock()
presenter = TeamViewPresenter(with: viewMock)
}
func testGetData() {
setupPresenter()
presenter.getData(completion: {_ in })
XCTAssertTrue(viewMock.isStart) // This one works and returns true
XCTAssertTrue(viewMock.isStop) // Return error
XCTAssertTrue(viewMock.isEndRefreshing) // Return error
XCTAssertTrue(viewMock.isReload) // Return error
}
}
class TeamViewMock: TeamViewPresenterProtocol {
var isStart = false
var isStop = false
var isEndRefreshing = false
var isReload = false
func startAnimating() {
self.isStart = true // Testing stops here and doesn't go any further...
}
func stopAnimating() {
self.isStop = true
}
func endRefreshing() {
self.isEndRefreshing = true
}
func reloadView(_ teams: NHLDTO) {
self.isReload = true
}
}
class TeamViewPresenter {
// MARK: - Public Properties
private weak var view: TeamViewPresenterProtocol?
public let dataFetcherService = DataFetcherService()
// MARK: - Initializers
init(with view: TeamViewPresenterProtocol) {
self.view = view
}
// MARK: - Public Methods
public func getData(completion: #escaping (AppError) -> Void) {
view?.startAnimating() // Testing stops here and doesn't go any further, but still returns true for the property isStart and error for the rest
dataFetcherService.fetchTeamData { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
completion(error)
print(error)
case .success(let teams):
guard let teams = teams else { return }
self.view?.reloadView(teams)
self.view?.stopAnimating()
self.view?.endRefreshing()
}
}
}
}
protocol TeamViewPresenterProtocol: AnyObject {
func startAnimating()
func stopAnimating()
func reloadView(_ teams: NHLDTO)
func endRefreshing()
}

how to init() a swift view controller properly?

I'm attempting to initialize this ViewController class. I am not using the MVC design strategy so ignore the bad conventions used (if any).
How do I initialize this class properly?
Error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIViewController'
Context: This is a calculator app that when any of the buttons are pressed. It will go find the senders title and simply put if one of the three vars are nil, it will store it in that optional.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBOutlet weak var answerLabel: UILabel!
//global vars for all funcs
var selection1: Int? {
didSet { answerLabel.text = String(selection1!) }
}
var selection2: String? {
didSet { answerLabel.text = selection2! }
}
var selection3: Int? {
didSet { answerLabel.text = String(selection3!) }
}
var answer: Int {
didSet { answerLabel.text = String(answer) }
}
init() {
}
#IBAction func touchButton(_ sender: UIButton) {
if selection1 == nil {
selection1 = Int(sender.currentTitle!)
print("Selection set in first pos.")
} else if selection2 == nil {
selection2 = sender.currentTitle
} else if selection3 == nil {
selection3 = Int(sender.currentTitle!)
} else {
calculate(firstNum: selection1!, operation: selection2!, secondNum: selection3!)
}
}
func calculate(firstNum: Int, operation: String, secondNum: Int) {
switch operation {
case "+":
answer = firstNum + secondNum
case "-":
answer = firstNum - secondNum
case "x":
answer = firstNum * secondNum
case "/":
answer = firstNum / secondNum
default:
answerLabel.text = "Something went wrong!"
}
}
}
Initialization depends on a couple of condition.
If you are using storyboard, you can just remove the init and your VC will have default initializer. Make sure either all of your properties have default value or they are optional.
If you are using xib or just creating view programmatically you can have custom convenience initializer where you pass some extra data this way.
class MyViewController: ViewController {
var answer: Int
convenience init(answer: Int) {
self.init()
self.answer = answer
// Do other setup
}
}
Your controller is being instantiated from the storyboard. A safe place to configure initial views is during the controller's call to viewDidLoad, ie:
override func viewDidLoad() {
super.viewDidLoad()
// configure your views and subviews here
}

Bind uitextfield value to viewModel using mvvm

I'm trying to bind a UITextField to a viewModel, however whatever i do i keep getting Cannot invoke 'bind' with an argument list of type '(to: EmailViewModel). What am i doing wrong?
SignUpViewModel
class SignUpViewModel {
let model: SignUpModel
private let disposeBag = DisposeBag()
let emailFieldViewModel = EmailViewModel()
init(model :SignUpModel) {
self.model = model
}
}
EmailViewModel
struct EmailViewModel : FieldViewModel {
var value: Variable<String> = Variable("")
var errorValue: Variable<String?> = Variable(nil)
let title = "Email"
let errorMessage = "Email is wrong"
func validate() -> Bool {
let emailPattern = "[A-Z0-9a-z._%+-]+#([A-Za-z0-9.-]{2,64})+\\.[A-Za-z]{2,64}"
guard validateString(value.value, pattern:emailPattern) else {
errorValue.value = errorMessage
return false
}
errorValue.value = nil
return true
}
}
viewcontroller
class SignUpViewController: UIViewController {
#IBOutlet var emailField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
configureBinding()
}
private func configureBinding() {
// binding
self.emailField.rx.text.bind(to: viewModel.emailFieldViewModel)
}
}
The bind function expects an object that conforms to the ObserverType protocol. Here, EmailViewModel does not conform to that type, hence the error.
Writing an extension to make EmailViewModel conform to the ObserverType protocol would solve the compilation error.
extension EmailViewModel: ObserverType {
func on(_ event: Event<String?>) {
switch event {
case .next(let newValue): value.value = newValue ?? ""
case .error(_), .completed: fatalError("Completion and error are not handled")
}
}
}

Call a func from another class in swift

I would like to call a function which is coded on another class.
So far I have made a struct on the file structs.swift for my data:
struct defValues {
let defCityName: String
let loadImages: Bool
init(defCity: String, loadImgs: Bool){
self.defCityName = defCity
self.loadImages = loadImgs
}
}
I have made the file Defaults.swift containing:
import Foundation
class DefaultsSet {
let cityKey: String = "default_city"
let loadKey: String = "load_imgs"
func read() -> defValues {
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.stringForKey(cityKey){
print(name)
let valuesToReturn = defValues(defCity: name, loadImgs: true)
return valuesToReturn
}
else {
let valuesToReturn = defValues(defCity: "No default city set", loadImgs: true)
return valuesToReturn
}
}
func write(city: String, load: Bool){
let def = NSUserDefaults.standardUserDefaults()
def.setObject(city, forKey: cityKey)
def.setBool(load, forKey: loadKey)
}
}
in which I have the two functions read, write to read and write data with NSUsersDefault respectively.
On my main ViewController I can read data with:
let loadeddata: defValues = DefaultsSet().read()
if loadeddata.defCityName == "No default city set" {
defaultCity = "London"
}
else {
defaultCity = loadeddata.defCityName
defaultLoad = loadeddata.loadImages
}
But when I try to write data it gives me error. I use this code:
#IBOutlet var settingsTable: UITableView!
#IBOutlet var defaultCityName: UITextField!
#IBOutlet var loadImgs: UISwitch!
var switchState: Bool = true
#IBAction func switchChanged(sender: UISwitch) {
if sender.on{
switchState = true
print(switchState)
}else {
switchState = false
print(switchState)
}
}
#IBAction func saveSettings(sender: UIButton) {
DefaultsSet.write(defaultCityName.text, switchState)
}
You need an instance of the DefaultsSet class
In the view controller add this line on the class level
var setOfDefaults = DefaultsSet()
Then read
let loadeddata = setOfDefaults.read()
and write
setOfDefaults.write(defaultCityName.text, switchState)
The variable name setOfDefaults is on purpose to see the difference.
Or make the functions class functions and the variables static variables and call the functions on the class (without parentheses)
From the code you posted, it seems you either need to make the write method a class method (just prefix it with class) or you need to call it on an instance of DefaultsSet: DefaultsSet().write(defaultCityName.text, switchState).
Another issue I found is that you also need to unwrapp the value of the textField. Your write method takes as parameters a String and a Bool, but the value of defaultCityName.text is an optional, so String?. This results in a compiler error.
You can try something like this:
#IBAction func saveSettings(sender: UIButton) {
guard let text = defaultCityName.text else {
// the text is empty - nothing to save
return
}
DefaultsSet.write(text, switchState)
}
This code should now compile and let you call your method.
Let me know if it helped you solve the problem

Best practice to store CurrentUser after login

I'm implementing my login-logic using Firebase with just Facebook as provider.
How can I save my CurrentUser after the login in order to use personal data during the app experience later?
At the moment I'm using a singleton with an instance of User. Something like this:
CurrentUser.swift
class CurrentUser {
static let i: CurrentUser = CurrentUser()
var cUser: User?
private init() {
}
func setCurrentUser(u: User) {
cUser = u
}
func getCurrentUser() -> User {
return cUser!
}
func clearUser() {
cUser = nil
}
func userIsLogged() -> Bool {
return cUser != nil
}
}
And I'm using that singleton this way:
LoginViewController.swift
class LoginViewController: UIViewController {
...
func createCurrentUser(authData: FAuthData) {
let u = User(uid: authData.uid, displayName: authData.providerData["displayName"] as! String, email: authData.providerData["email"] as! String)
u.wrapperFromFacebookData(authData.providerData)
ref.childByAppendingPath("users").childByAppendingPath(u.uid).setValue(u.toDict())
CurrentUser.i.setCurrentUser(u)
}
...
}
I don't think this is the best practice. Before Firebase I'm used to deal with Parse builtin user logic, that was pretty easier.
I am facing the exact problem and this link helped a lot: http://totallyswift.com/ios-app-development-part-2/
What he did was create a singleton (currentUser) that conforms with User class.
class var currentUser: User
{
struct Static
{
static var instance: User?
}
if Static.instance == nil
{
if let load: AnyObject = NSUserDefaults.standardUserDefaults().objectForKey(kUserDataKey)
{
Static.instance = User(data: load as [String: AnyObject])
}
else
{
Static.instance = User()
}
}
return Static.instance!
}