I've just started playing around with Rx and decided to try out making a simple OSX app using RxSwift.
Since my app has a login form, I've found that GithubSignup example is pretty similar to what I'm doing.
I'm, however having an issue that my Drivers get disposed after first value is emitted from them, and I can't figure out how or why. Since my code is really similar to one from the Github example, I must be overlooking something.
Here is my ViewModel:
class LoginVM {
let isWorking: Driver<Bool>
let loginEnabled: Driver<Bool>
init(
input: (
email: Driver<String>,
password: Driver<String>,
loginRequests: Driver<Void>
),
dependency: (
RoundedClient
)
) {
self.isWorking = Variable(false).asDriver()
let credentials = Driver
.combineLatest(input.email, input.password){ (email: $0, password: $1) }
let credentialsEmpty = credentials
.map{ credentials in
credentials.email.characters.count > 0 && credentials.password.characters.count > 0
}
.distinctUntilChanged()
self.loginEnabled = Driver
.combineLatest(credentialsEmpty, self.isWorking){ !($0 || $1) }
.distinctUntilChanged()
}
}
And here is my ViewController:
class LoginViewController: NSViewController {
var screenManager: ScreenManager!
#IBOutlet weak var emailField: NSTextField!
#IBOutlet weak var passwordField: NSSecureTextField!
#IBOutlet weak var loginButton: NSButton!
#IBOutlet weak var loginSpinner: NSProgressIndicator!
#IBOutlet weak var errorLabel: NSTextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let vm = LoginVM(
input: (
email: self.emailField.rx_text.asDriver(),
password: self.passwordField.rx_text.asDriver(),
loginRequests: self.loginButton.rx_tap.asDriver()
),
dependency: RoundedClient.sharedInstance
)
vm.loginEnabled
.driveNext{ [weak self] enabled in
self?.loginButton.enabled = enabled
self?.loginButton.alphaValue = enabled ? 1.0 : 0.5
}.addDisposableTo(self.disposeBag)
vm.isWorking
.drive(self.loginSpinner.ex_animating)
.addDisposableTo(self.disposeBag)
}
}
Here is an example when I attach ".debug()" to credentialsEmpty driver on LoginVM:
2016-04-17 16:32:36.730: LoginViewController.swift:38 (init(input:dependency:)) -> subscribed
2016-04-17 16:32:36.731: LoginViewController.swift:38 (init(input:dependency:)) -> Event Next(false)
2016-04-17 16:32:39.081: LoginViewController.swift:38 (init(input:dependency:)) -> Event Next(true)
2016-04-17 16:32:39.081: LoginViewController.swift:38 (init(input:dependency:)) -> disposed
It is getting disposed as soon as value is emitted after initial one.
move your LoginVM instance variable to class member field.
let vm = LoginVM( ...
above 'vm' instance has locality in viewDidLoad() function
I had this problem because I wrote
let disposeBag = DisposeBag()
inside the viewDidLoad method.
Which effectively brought it out of scope as soon as the method finished.
Related
I'm a bit of a newb here, so please be kind. I'm a former Air Force pilot and am currently in law school, so coding is not my full time gig...but I'm trying to learn as I go (as well as help my kiddos learn).
I'm working on a profile page for my iOS app. I've gone through the firebase documentation quite extensively, but it just doesn't detail what I'm trying to do here. I've also searched on this site trying to find an answer...I found something that really helped, but I feel like something is just not quite right. I posted this previously, but I deleted because I did not receive any helpful input.
What I'm trying to do is display the user's data (first name, last name, phone, address, etc.) via labels. The code (provided below) works to show the user id and email...I'm thinking this is because it is pulled from the authentication, and not from the "users" collection. This code is attempting to pull the rest of the user's data from their respective document in the users collection.
Here is the full code for the viewController. I've tried and failed at this so many times that I'm really on my last straw...hard stuck! Please help!
My guess is that something is not right with the firstName variable...whether that be something wrong with the preceding database snapshot, or with the actual coding of the variable. But then again...I don't know what I'm doing...so perhaps I'm way off on what the issue is.
// ClientDataViewController.swift
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class ClientDataViewController: UIViewController {
#IBOutlet weak var firstNameLabel: UILabel!
#IBOutlet weak var lastNameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var phoneLabel: UILabel!
#IBOutlet weak var streetLabel: UILabel!
#IBOutlet weak var street2Label: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var stateLabel: UILabel!
#IBOutlet weak var zipLabel: UILabel!
#IBOutlet weak var attorneyLabel: UILabel!
#IBOutlet weak var updateButton: UIButton!
#IBOutlet weak var passwordButton: UIButton!
#IBOutlet weak var uidLabel: UILabel!
let id = Auth.auth().currentUser!.uid
let email = Auth.auth().currentUser!.email
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.uidLabel.text = id
self.emailLabel.text = email
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) // call super
getName { (name) in
if let name = name {
self.firstNameLabel.text = name
print("great success")
}
}
}
// MARK: Methods
func getName(completion: #escaping (_ name: String?) -> Void) {
let uid = "dL27eCBT70C4hURGqV7P"
let docRef = Firestore.firestore().collection("users").document(uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
} else {
print("Document does not exist")
}
completion("put the first name data here after we figure out what's in the doc")
}
}
}
The following with solve your problems. However, I'd advise against declaring id and email as force-unwrapped instance properties; they don't even need to be instance properties, let alone force unwrapped. Always safely unwrap optionals before using their values, especially these authorization properties because if the user isn't signed in or is signed out underneath you (expired token, for example), the app would crash here and, as with flying planes, crashing is always to be avoided.
class ClientDataViewController: UIViewController {
#IBOutlet weak var firstNameLabel: UILabel!
#IBOutlet weak var lastNameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var phoneLabel: UILabel!
#IBOutlet weak var streetLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var stateLabel: UILabel!
#IBOutlet weak var zipLabel: UILabel!
#IBOutlet weak var attorneyLabel: UILabel!
#IBOutlet weak var updateButton: UIButton!
#IBOutlet weak var passwordButton: UIButton!
#IBOutlet weak var uidLabel: UILabel!
let id = Auth.auth().currentUser!.uid
let email = Auth.auth().currentUser!.email
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.uidLabel.text = id
self.emailLabel.text = email
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) // call super
getName { (name) in
if let name = name {
self.firstNameLabel.text = name
print("great success")
}
}
}
// MARK: Methods
func getName(completion: #escaping (_ name: String?) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { // safely unwrap the uid; avoid force unwrapping with !
completion(nil) // user is not logged in; return nil
return
}
Firestore.firestore().collection("users").document(uid).getDocument { (docSnapshot, error) in
if let doc = docSnapshot {
if let name = doc.get("firstName") as? String {
completion(name) // success; return name
} else {
print("error getting field")
completion(nil) // error getting field; return nil
}
} else {
if let error = error {
print(error)
}
completion(nil) // error getting document; return nil
}
}
}
}
And thank you for your service! Hopefully you got to fly a B1-B.
I suspect from the evidence in your question that you are getting a doc, but have an incorrect field name or an uninitialized field in the retrieved doc. As a debug step, replace your getName function with this one, which prints all of the data found in the doc.
func getName(completion: #escaping (_ name: String?) -> Void) {
let uid = Auth.auth().currentUser!.uid
let docRef = Firestore.firestore().collection("users").document(uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
} else {
print("Document does not exist")
}
completion("put the first name data here after we figure out what's in the doc")
}
}
Once we know what's in the doc, it should be easy to work out what value to pass to the completion function.
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) {
...
}
I have been practicing a login page and the creation of an account with Firebase. I am successfully signing up a user and saving the user in firebase. Now I am trying to save a first and last name to the user id when they create their account. I've tried looking at other SO answers, but still can't seem to get this to work.
I have been going through multiple tutorials, and have tried multiple unwrapping attempts, but keep running into this error. Below is my view controller:
View Controller
import UIKit
import Firebase
class ViewController: UIViewController {
var ref: DatabaseReference!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var firstNameField: UITextField!
#IBOutlet weak var lastNameField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
var ref = Database.database().reference()
}
#IBAction func createAccountTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text, let firstName = firstNameField.text, let lastName = lastNameField.text {
Auth.auth().createUser(withEmail: email, password: password ) { (user, error) in
// ...
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
//add popup later
}
let userId = user!.uid
self.ref.child("users").child(userId).setValue(["firstName": firstName])
print("User registered in Firebase with a userId of " + user!.uid)
}
}
}
Where am I going wrong? I thought I was unwrapping the variables at the top, with my 'if let'. I tried force unwrapping them individually, as well, but keep having the same error. A bit lost.
in viewDidLoad(), you call
var ref = Database.database().reference()
but it should be
ref = Database.database().reference()
Swift is treating it like a different variable that you're declaring within the scope of viewDidLoad(), so when you go to use ref, it still has no value.
Please change your Database reference from
var ref: DatabaseReference!
to
var databaseRef = Database.database().reference()
and then do
self.child("users").child(userId).setValue(["firstName": firstName])
or in you viewDidLoad do
self.ref = Database.database().reference()
This error happens because you are not initialising your Database reference
This should work without any problems
import UIKit
import Firebase
import FirebaseDatabase
class ViewController: UIViewController {
var ref: DatabaseReference!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var firstNameField: UITextField!
#IBOutlet weak var lastNameField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.ref = Database.database().reference()
}
#IBAction func createAccountTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text, let firstName = firstNameField.text, let lastName = lastNameField.text {
Auth.auth().createUser(withEmail: email, password: password ) { (user, error) in
// ...
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
//add popup later
}
let userId = user!.uid
self.ref.child("users").child(userId).setValue(["firstName": firstName])
print("User registered in Firebase with a userId of " + user!.uid)
}
}
}
I'm having a little trouble in getting PreLoaded data to work. At first, here is code:
if firstRun {
userDefaults.set(1, forKey: "dayCount")
userDefaults.set(dateFormatter.string(from: date), forKey: "date")
let newPath = defaultPath.deletingLastPathComponent()
let v0Path = bundleURL("default")
do {
//try FileManager.default.removeItem(at: defaultPath)
try FileManager.default.copyItem(at: v0Path!, to:defaultPath)
} catch {
print(error)
}
} ...
v0Path:
file:///Users/dimasalbuquerque/Library/Developer/CoreSimulator/Devices/7FE635BA-AA7A-4241-AF3B-88AD60693AE7/data/Containers/Bundle/Application/B6739B92-B7D8-4DD8-9DA8-CD9BBD84B109/Example.app/default.realm
defaultPath:
file:///Users/dimasalbuquerque/Library/Developer/CoreSimulator/Devices/7FE635BA-AA7A-4241-AF3B-88AD60693AE7/data/Containers/Data/Application/A86C4337-9006-497C-A688-AD781F49EF04/Documents/default.realm
I followed this guide: https://github.com/realm/realm-cocoa/blob/master/examples/ios/swift-2.2/Migration/AppDelegate.swift
Problem is that when the program is running by the first time, it executes the code correctly, but when I try to access realm database, it says it's empty. Although when I open the app for the second time, it works, the data is there. It's already over 1 week that I'm trying to solve this, I've searched through all the net but without success.
Here is where Realm is first called:
class HomeViewController: UIViewController {
#IBOutlet weak var message: UILabel!
#IBOutlet weak var backward: UIButton!
#IBOutlet weak var forward: UIButton!
#IBOutlet weak var background: UIImageView!
#IBOutlet weak var timeBtn: UIButton!
#IBOutlet weak var favoriteBtn: UIButton!
#IBOutlet weak var googleAd: GADBannerView!
let userDefaults = UserDefaults.standard
let realm = try! Realm()
var currentDate = 1
var time = 0 {
didSet {
if time != oldValue {
randomBackground(time)
}
}
}
var dailyMessage: DailyMessagesRealm?
var currentMsg: Message?
override func viewDidLoad() {
super.viewDidLoad()
let first = userDefaults.bool(forKey: "notFirstRun")
if !first {
userDefaults.set(true, forKey: "notFirstRun")
reNew()
}
let day = userDefaults.integer(forKey: "dayCount")
currentDate = day
let empty = realm.objects(DailyMessagesRealm.self).isEmpty
let dailyMessage = realm.objects(DailyMessagesRealm.self).filter("date == '\(day)'").first
//*********Error occurs here***********
self.dailyMessage = dailyMessage!
self.currentMsg = dailyMessage?.morningMessage
self.currentMsg = dailyMessage?.morningMessage
changeMessage((dailyMessage?.morningMessage?.message)!)
initAds()
changeBackground("morning1")
checkFavorite()
} ...
From the sound of it, you must be calling Realm() somewhere before you're performing your copy operation here.
default.realm is only opened when you call Realm() for the first time. Once it's open though, it stores cached information about the file in memory, so if you replace it after the fact, you'll end up with unpredictable behavior.
If you absolutely need to do some kind of operation with Realm(), you can enclose it in an #autoreleasepool { } block to ensure its cached entries in memory are predictably flushed before you do the file copy.
Other than that, I recommended checking your code to ensure you're performing this file copy before touching any instances of Realm() pointing at it.
New to watch development however....
My app gets the user to select a duration for a countdown timer using a slider on one interface controller as shown below:
class game_settings: WKInterfaceController {
#IBOutlet weak var halflength: WKInterfaceSlider!
#IBOutlet weak var halflengthdisplay: WKInterfaceLabel!
#IBOutlet var sethalflengthbutton: WKInterfaceButton!
#IBAction func halfsliderdidchange(value: Float) {
halflengthdisplay.setText("\(value)")
}
override func contextForSegueWithIdentifier(initialhalftogame: String) -> AnyObject? {
// You may want to set the context's identifier in Interface Builder and check it here to make sure you're returning data at the proper times
// Return data to be accessed in ResultsController
return self.halflengthdisplay
}
}
i got this from the following question: Passing data question
then i want the selected interval to be used for the timer on another interface controller as shown below.
class main_game_controller: WKInterfaceController {
#IBOutlet weak var WKTimer: WKInterfaceTimer!//timer that the user will see
var internaltimer : NSTimer?
var ispaused = false
var elapsedTime : NSTimeInterval = 0.0
var StartTime = NSDate()
#IBOutlet var extratime_button: WKInterfaceButton!
#IBOutlet var endgame_button: WKInterfaceButton!
#IBOutlet var sanction_button: WKInterfaceButton!
#IBOutlet var goal_button: WKInterfaceButton!
#IBOutlet var additional_time_timer: WKInterfaceTimer!
#IBOutlet var reset_timer_button: WKInterfaceButton!
#IBOutlet var stop_timer_button: WKInterfaceButton!
#IBOutlet var Start_timer_button: WKInterfaceButton!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
var halflengthinterval : NSTimeInterval// variable was written to, but never read
// Configure interface objects here.
if let halflength: String = context as? String {
halflengthinterval = Double(halflength)!
}
}
override func willActivate() {
super.willActivate()
}
#IBAction func Start_button_pressed() {
internaltimer = NSTimer.scheduledTimerWithTimeInterval(halflengthinterval, target:self, selector: Selector("timerdone"), userInfo: nil, repeats:false) //use of unfesolved identifier"halflengthinterval"
WKTimer.setDate(NSDate(timeIntervalSinceNow: halflengthinterval))
WKTimer.start()//use of unresolved identifier "halflengthinterval"
}
#IBAction func stop_timer_button_pressed() {
if ispaused{
ispaused = false
internaltimer = NSTimer.scheduledTimerWithTimeInterval(halflengthinterval - elapsedTime, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)//use of unresolved identifier 'halflengthinterval'
WKTimer.setDate(NSDate(timeIntervalSinceNow: halflengthinterval - elapsedTime))//use of unresolved identifier 'halflengthinterval'
WKTimer.start()
StartTime = NSDate()
stop_timer_button.setTitle("Pause")
}
else{
ispaused = true
//get how much time has passed before they paused it
let paused = NSDate()
elapsedTime += paused.timeIntervalSinceDate(StartTime)
//stop watchkit timer on screen
WKTimer.stop()
//stop internal timer
internaltimer!.invalidate()
//ui modification
stop_timer_button.setTitle("Resume")
}
}
I was following the answer provided in this question: WKInterface implementation
as you can see in the commented lines above, I'm receiving several errors associated with the variable halflengthinterval. I get the feeling that I'm not correctly passing the interval value between the two interface controllers, but for the life of me i have no idea how to do it.
Could someone please help me in showing
how to pass the value for the timer from the first interface
controller to the second interface controller and
how to correctly set the countdown timer for the length of time selected by the slider in the first interface controller.
Thanks very much!
Let's fix first the error regarding to NSInterval, NSInterval is just a typealis for the type Double:
typealias NSTimeInterval = Double
So the problem you're facing is how to convert a String to a Double and the way is using the Double constructor like in this way:
Double(IntValue)
Regarding how to pass data from two WKInterfaceController you're doing in the right way, but you have one mistake to fix. If you want to pass data from one WKInterfaceController to another WKInterfaceController using segues you can use the contextForSegueWithIdentifier, but in your case you are returning a NSInterval type or Double and then you're trying to cast as an String and this fail in this line of code always:
// Configure interface objects here.
if let halflength: String = context as? String {
halflengthinterval = Double(halflength)!
}
You have to change it to this line instead using the guard statement if you like or using optional-binding, it's up to you:
guard
guard let halflength = context as? Double else {
return
}
self.halflengthinterval = Double(halflength)
optional-binding
if let halflength = context as? Double {
self.halflengthinterval = Double(halflength)
}
I hope this help you.