Preloaded database with Realm Swift - swift

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.

Related

Fetch Data From Firestore for User Profile (swift, iOS, Xcode, firebase/firestore)

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.

Update NSTextField based on selection in NSPopupButton

I've only been coding for a few weeks so forgive my no-doubt clumsy code.
I have a NSTextField that needs to update based upon a selection from an NSPopupButton
These are my outlets:
#IBOutlet weak var inputText: NSTextField!
#IBOutlet weak var outputText: NSTextField!
#IBOutlet weak var inputBrowseButton: NSButton!
#IBOutlet weak var outputBrowseButton: NSButton!
#IBOutlet weak var codecSelector: NSPopUpButton!
These are my variables:
// derive default path and filename from #IBAction of inputBrowseButton
var defaultUrl: URL!
// derived from default of inputBrowse unless outputBrowse is used
var outputUrl: URL!
// change output path from #IBAction of outputBrowseButton
var outputDirectory: URL!
// change path extension from #IBAction of codecSelector NSPopupButton
var codecChosen: String = ""
// dictionary of arguments to form script for ffmpeg
var ffmpegCodecArg: String = ""
And here is my method to pull all that together:
// construct output path for use in ffmpegArguments dictionary and display in outputText NSTextField
func updateOutputText(defaultUrl: URL?, outputDirectory: URL?) -> String {
if defaultUrl != nil && outputDirectory != nil {
let outputFile = defaultUrl!.deletingPathExtension()
let outputFilename = outputFile.lastPathComponent
outputUrl = outputDirectory!.appendingPathComponent(outputFilename)
outputText.stringValue = outputUrl.path + "\(codecChosen)"
} else if defaultUrl == nil && outputDirectory != nil {
outputText.stringValue = outputDirectory!.path
} else {
outputUrl = defaultUrl!.deletingPathExtension()
outputText.stringValue = outputUrl.path + "\(codecChosen)"
}
return outputText.stringValue
}
Now at the present this function isn't working because I haven't figured out how to call it yet. But that's an issue for another time, not what I'm asking about here.
Previously I was running
outputUrl = defaultUrl!.deletingPathExtension()
outputText.stringValue = outputUrl.path + "\(codecChosen)"
as part of my inputBrowseButton #IBAction method, and I was running
let outputFile = defaultUrl!.deletingPathExtension()
let outputFilename = outputFile.lastPathComponent
outputUrl = outputDirectory!.appendingPathComponent(outputFilename)
outputText.stringValue = outputUrl.path + "\(codecChosen)"
as part of my outputBrowseButton #IBAction method.
Which worked fine, EXCEPT when it came to updating outputText.stringValue when the codecChosen variable was assigned a new value in my #IBAction for the codecSelector NSPopupButton method.
I suspect the problem is that I don't have my outputText NSTextField set up to update when the codecSelector NSPopupButton changes. What do I need to do to make that happen?
How do you set the text in an NSTextField?
Which worked fine, EXCEPT when it came to updating outputText.stringValue when the codecChosen variable was assigned a new value in my #IBAction for the codecSelector NSPopupButton method.
Try using a separate method to change the text.
func changeText(inputString: String, inputField: NSTextField) {
inputField.stringValue = inputString
}
Separating your code more might make it easier for you to keep track of while you are starting out. I am not entirely familiar with macOS, so that method may not work, but try working with the principle of it and see if you can progress.

RxSwift Driver gets disposed after first value

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.

setting the countdown interval of a WatchKit timer

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.

swift save multiple manage objects

Having issues saving my manage objects within my code. For some reason when i place data in the first view controller everything works well. For instance
I place new categories such as "Fruits", "Dairy", "Meats". The first view controller takes the data. When I click on the specific item such as "Dairy", and put in "Milk" for items within that section. If I go back to the previous view controller and click on "Meats", I see the same data i put in under "Dairy". How do i properly manage my NSManage objects.
Here is my code below.
import UIKit
import CoreData
class HomeSpecificItemViewController: UIViewController {
var selectedItem : [Items] = []
#IBOutlet weak var itemNameTextField: UITextField!
#IBOutlet weak var brandNameTextField: UITextField!
#IBOutlet weak var caloriesTextField: UILabel!
#IBOutlet weak var priceTextField: UILabel!
#IBOutlet weak var amountTextField: UITextField!
#IBOutlet weak var threshHoldNumberField: UITextField!
#IBOutlet weak var stepper: UIStepper!
override func viewDidLoad() {
super.viewDidLoad()
stepper.wraps = true
stepper.autorepeat = true
stepper.maximumValue = 10
// Do any additional setup after loading the view.
}
#IBAction func saveButton(sender: AnyObject) {
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let itemDescription = NSEntityDescription.insertNewObjectForEntityForName("Items", inManagedObjectContext: context) as! Items
itemDescription.setValue(itemNameTextField.text, forKey: "Items")
itemDescription.setValue(brandNameTextField.text, forKey: "Items")
do {
try context.save()
}catch _ {
}
/*
let request = NSFetchRequest(entityName: "Items")
let results : [AnyObject]?
do {
results = try context.executeFetchRequest(request)
}catch _ {
results = nil
}
if results != nil {
self.itemDescription = results as! [Items]
}
*/
}
#IBAction func cancelPressed(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func increaseNumberStepper(sender: UIStepper) {
threshHoldNumberField.text = Int(sender.value).description
}
}
Do you have a specific view controller for each category? If so, what you have to do is add predicates to your more specific view controllers.
Something like:
var request = NSFetchRequest(entityName: "Food")
request.predicate = NSPredicate(format: "category == %#", "Meat")
meats = try! context.executeFetchRequest(request)
This would return an array of all Food objects whose category atribute holds the string "Meat".
I was saving my data to core data without properly declaring the manage context and without assigning the text labels to the core data object.
issue resolved!