I was wondering if someone could explain the concept of "cast" or "casting" to me regarding programming and also help me with the issue of getting "Swift dynamic cast failed" when trying to execute this code:
import UIKit
import CoreData
class vcMain: UIViewController {
#IBOutlet var txtUsername: UITextField!
#IBOutlet var txtPassword: UITextField!
#IBAction func btnSave(){
//println("Save button pressed \(txtUsername.text)")//Adds the text written, in the console
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObjectContext
newUser.setValue("Test Username", forKey: "username")
newUser.setValue("Test Password", forKey: "password")
context.save(nil)
println(newUser)
println("Object Saved.")
}
#IBAction func btnLoad(){
//println("Load button pressed \(txtPassword.text)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm following a tutorial on YouTube but cant figure out why its not working for me!
This line looks wrong:
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObjectContext
You're casting to a NSManagedObjectContext when you probably meant to cast to a NSManagedObject, so the line should be:
var newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) as NSManagedObject
I agree with Jack Lawrence's answer.
We are looking at the function NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context) which, from the documentation, will return a objC type, id, which kinda translates to Swift as an AnyObject. This is to say the computer doesn't know what it is, but we do, because we read the documentation:
insertNewObjectForEntityForName:inManagedObjectContext:
Creates, configures, and returns an instance of the class for the entity with a given name.
Return Value
A new, autoreleased, fully configured instance of the class for the entity named entityName. The instance has its entity description set and is inserted it into context.
Anyway, you can be safe to assume it is returning a type NSManagedObject, or some subclass of it.
Now about casting:
Most of this is from the Swift iBook. So you already know that type casting / downcasting in swift is done by the as operator. Because downcasting can fail, the type cast operator comes in two forms. 1) Forced as and 2) Optional as?
Basically the differnce is, use as? when you are not sure if the downcast will succeed, and use as when you are certain it will. Here is an example, where I have an array of type AnyObject called Vehicles..., which contains two different classes, Car and Truck, of which there are 5 cars and 2 trucks.
var carCount = 0
var truckCount = 0
for vehicle in vehicles{
if let car = car as? Car
{
carCount++
} else
if let truck = truck as? Truck
{
truckCount++
}
}
At the end of this my counts are correct, and I have not thrown an error. But, trying to access any one of the elements of vehicles, like vehicles[i] as Car would be dangerous, because it might be a truck, and you are forcefully saying that it is a car. Runtime error!
Anyway I hope that explains it a bit. If not, check out the chapter on type casting in the Swift iBook, page 391.
For anyone else out there with a similar problem. There is a little gotcha here.
If you are trying to generate a custom NSManagedObject subclass using the NSEntityDescription.insertNewObjectForEntityForName(Entity, inManagedObjectContext: context) method, then make sure that the swift class has the #objc() header added to the class. Otherwise you'll encounter a dynamic class cast failure also.
Related
When I go back to VC1 (which allows the user to input a title for a book and create an entry in realm including the title and a UUID for that book) from VC2 (using the provided back button as part of the navigation controller not a custom segue) and then create a new book object in Realm (by adding another title to the text field in VC1), the app crashes saying I cannot amend the primary key once set.
I am intending to create a new entry (in theory I could add one, go back, add another etc) rather than try to overwrite an existing entry.
I've read the docs (and even looked at an old project where a similar thing is working) but I can't understand why it isn't just creating a new entry. I looked at the Realm docs (e.g. referenced in this answer Realm in IOS: Primary key can't be changed after an object is inserted)
Code here is VC1 allowing the user to create a new novel (by adding a title into a text field which is earlier in the code)
func createNewNovel() {
let realm = try! Realm()
novelCreated.novelID = UUID().uuidString
novelCreated.novelTitle = novelTitleInput.text!
novelCreated.createdDate = Date()
do {
try realm.write {
realm.add(novelCreated)
print ("Novel Created Successfully")
}
} catch {
print("error adding novel")
}
Then I prepare to pass the novelID to VC2 :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let novelData = NovelObject()
novelData.novelID = novelCreated.novelID
if let destVC = segue.destination as? WriteVC {
destVC.novelIDPassed = novelData.novelID
}
}
This works fine and the segue triggers from a button press on VC1. I can also print the ID of the novel in VC2 so that's working fine.
BUT... when I go back from VC2 and input a new title in the text field in VC1 it should create a new NovelObject, not try to update the existing one (which is causing it to crash). You could even theoretically have the same book title twice but they should have a different UUID (which is the primary key)
I must be missing something trivial I suppose but can't work it out!
The novel is created as at the top :
class NewNovelVC: UIViewController {
let novelCreated = NovelObject()
#IBOutlet weak var novelTitleInput: UITextField!
#IBOutlet weak var createButtonOutlet: UIButton!
then it is populated with variables
This is due to classic issue of same object being in the memory re-instantiated which points to the same value of primary key. Creating a singleton class can be very handy here as well.
Create a service file. This will keep your RealSwift initialized on a specific thread as your current block.
RealService.swift
import RealmSwift
class RealmService {
static let uirealm = RealmService()
private var _initRS = try! Realm()
var realm: Realm! {
return _initRS
}
}
Novel.swift :
import RealmSwift
class Novel : Object {
#objc dynamic var uid : String? = nil
#objc dynamic var title: String? = nil
override static func primaryKey() -> String {
return "uid"
}
}
extension Novel {
func writeToRealm(){
try? RealmService.uirealm.realm.write {
print("Creating the Novel Object")
RealmService.uirealm.realm.add(self, update: true)
}
}
func DeleteFromRealm(object: Results<Novel>){
try? RealmService.uirealm.realm.write {
print("Deleting the Novel Object")
RealmService.uirealm.realm.delete(object)
}
}
}
and just implement as #Don mentioned, write in the block where you are setting the title.
var novel: Novel! // Declare this in above in the class.
novel = Novel()
novel.title = title
novel.uid = uid
novel.writeToRealm()
Hope that helped.
I'm new to Swift, and asynchronous code in general, so tell me if this is way off. Basically I want to:
Open the App
That triggers a read of CloudKit records
Once the read is complete a UILabel will display the number of records retrieved
This clearly isn't useful in itself, but as a principle it will help me to understand the asynchronous code operation, and how to trigger actions on their completion.
// In ViewController Swift file:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
readDatabase()
}
#IBOutlet weak var myLabel: UILabel!
}
let VC=ViewController()
//In Another Swift file:
func readDatabase() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "myRecord", predicate: predicate)
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
VC.myLabel.text = ("\(allRecs?.count) records retreived")
/*
ERROR OCCURS IN LINE ABOVE:
CONSOLE: fatal error: unexpectedly found nil while unwrapping an Optional value
BY CODE LINE: Thread 8:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
*/
}
}
I'm able to set the text field from within the viewDidLoad function, so why not from a function called within that function?
A few other things I've tried:
Use async dispatch to put it on thread 1
Implement a var with within the ViewController class, with a didSet that sets the text, set the var to the desired value in the privateDB.perform code to trigger the change
These both create the same problem as above.
Yes, I know there isn't any error handling in .perform, and yes there are records. If I trigger the setting of the UILabel text to the record count manually a few seconds after the view has loaded, it works fine.
So the question is...
How do I use the completion of the database read as a trigger to load attributes of the records to the view?
Thanks
Edit
What actually happened here was that VC was created globally, but never presented - since loadView was never called for it, myLabel didn't exist and, being a force unwrapped property, caused a crash when it was referenced
The problem is with this line: let VC=ViewController(). Here you instantiate a new instance of your ViewController class and try to set the label on that newly created instance. However, you would want to set the label on your viewController instance that is currently displayed.
Just change this line VC.myLabel.text = ("\(allRecs?.count) records retreived") to self.myLabel.text = ("\(allRecs?.count) records retreived") and it should work fine.
Got it, the solution looks like this:
// In ViewController Swift file:
typealias CompletionHandler = (_ recCount:Int,_ err:Error?) -> Void
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
readDatabase(completionHandler: { (recCount,success) -> Void in
if err == nil {
self.myLabel.text = "\(recCount) records loaded"
} else {
self.myLabel.text = "load failed: \(err)"
}
})
}
#IBOutlet weak var myLabel: UILabel!
}
//In Another Swift file:
func readDatabase() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "myRecord", predicate: predicate)
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
privateDB.perform(query, inZoneWith:nil) { (allRecs, err) in
if let recCount = allRecs?.count {
completionHandler(recCount,err)
} else {
completionHandler(0,err)
}
}
}
The difference between this and the original is that this uses the CompletionHandler typealias in the function call for loading the database records, which returns the count of records and an optional error.
The completion operation can now live in the ViewController class and access the UILabel using self.myLabel, which solves the error that was occurring earlier, while keeping the database loading code separate to the ViewController class.
This version of the code also has basic error handling.
I am getting some user information from Firebase and storing it into singleton. After that every time the value changes I want that the label changes also but it doesn't until I terminate the app and come back in.
How should I update label if value changes in singleton?
I have tab views. In first tab I assign values and in second tab I try to put the values to label.
This is my singleton:
class CurrentUser: NSObject
{
var generalDetails: User = User()/// Consecutive declarations on a line must be separated by ';'
static let sharedInstance = CurrentUser()
fileprivate override init(){
super.init()
}
}
And like this I assign values:
self.databaseRef.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
guard let firebaseValue = snapshot.value as? [String:AnyObject], let userName = firebaseValue["username"] as? String, let email = firebaseValue["email"] as? String, let reputation = firebaseValue["reputation"] as? Int, let profilePicURL = firebaseValue["profileImageUrl"] as? String
else
{
print("Error with FrB snapshot")//Here
return
}
//Set values
self.currentUser.generalDetails = User(userName: userName, email: email, reputation: reputation, profileImageURL: profilePicURL, uid: user.uid)
}
And if I want to put the value to the label I simply do this(This reputation is the only thing that can change often):
self.reputationLabel.text = String(self.currentUser.generalDetails.reputation)
You can do either of these:-
Communicate between the singleton and your class with delegate-protocol method , fire the delegate method in the class whenever your repo changes and update your label.
Open a different thread in your network link for the user's reputation in the viewController itself:-
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().reference().child("users").child(FIRAuth.auth()!.currentUser!.uid).child("reputation").observe(.childChanged, with: {(Snapshot) in
print(Snapshot.value!)
//Update yor label
})
which will get called every time the value of reputation changes.
I like Dravidian's answer and I would like to offer an alternative: KVO
We use Key-Value Observing to monitor if our app is disconnected from the Firebase server. Here's the overview.
We have a singleton which stores a boolean variable isConnected, and that variable is set by observing a special node in Firebase
var isConnected = rootRef.child(".info/connected")
When connected/disconnected, the isConnected var changes state.
We have a little icon on our views that indicates to the user the connected state; when connected it's green, when disconnected it's red with a line through it.
That icon is a class and within each class we have code that observes the isConnected variable; when it's state changes all of the icons change automatically.
It takes very little code, is reusable, is clean and easily maintained.
Here's a code snippet from the Apple documentation
//define a class that you want to observe
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
//Create a global context variable.
private var myContext = 0
//create a class that observes the myDate var
// and will be notified when that var changes
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
objectToObserve.addObserver(self,
forKeyPath: "myDate",
options: .new,
context: &myContext)
There will be more to it but that's it at a 10k foot level.
The Apple documentation is here
Using Swift with Cocoa and Obj-c 3.01: Design Patterns
and scroll down the the Key-Value Observing Section. It's a good read and very powerful. It follows the same design pattern as Firebase (or vice-versa) - observe a node (variable) and tell me when it changes.
Overview
I need to save several TextFields into CoreData, but only the first one (Seen as pickerView below) saves and prints correctly. The others do not save correctly, for instance, when I try to save the integer ones, I get an error saying that they cannot take a String, which makes sense. I just cannot find a way to fix the integer-string issue. The other error occurs when I attempted to cast everything as a string ( mainly because I won't need to do any arithmetic on it, so it doesn't matter ), and it just gives me a breaking point in the saveButton function.
What I would like to know
What I ultimately need is the ability to save all of these TextFields into CoreData so that I can later retrieve them. I appreciate the help in advance. Thank you!
NOTE
I am including the entire ( or most of ) the ViewController.swift file so that you can see how I am declaring things and then how they are being called. The code in question is located in the saveButton action at the bottom of the code block.
CODE
#IBOutlet weak var locationOfMachine: UITextField!
#IBOutlet weak var engineHours: UITextField!
#IBOutlet weak var YOM: UITextField!
#IBOutlet weak var serialNo: UITextField!
#IBOutlet weak var modelName: UITextField!
#IBOutlet weak var pickerTextField: UITextField!
var pickOption = ["Wirtgen","Kleeman","Hamm","Vögele"]
override func viewDidLoad() {
super.viewDidLoad()
var pickerView = UIPickerView()
pickerView.delegate = self
pickerTextField.inputView = pickerView
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
#IBAction func saveButton(sender: AnyObject)
{
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var entity1 = NSEntityDescription.insertNewObjectForEntityForName("UsedInfo", inManagedObjectContext:context) as NSManagedObject
entity1.setValue(pickerTextField.text, forKey: "product")
entity1.setValue(modelName.text, forKey:"modelName")
entity1.setValue(serialNo.text, forKey:"serialNo")
entity1.setValue(Int(YOM.text!), forKey:"yom")
entity1.setValue(engineHours.text, forKey:"engineHours")
entity1.setValue(locationOfMachine.text, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
do {
try context.save()
}
catch {
print("error")
}
}
EDIT
Upon trying to save everything as just a string, since i only need to retrieve it, I run into this issue:
entity1.setValue(pickerTextField.text, forKey: "product")
entity1.setValue(modelName.text, forKey:"modelName")
entity1.setValue(serialNo.text, forKey:"serialNo") <-Thread1:Breakpoint1.1
entity1.setValue(YOM.text, forKey:"yom")
entity1.setValue(engineHours.text, forKey:"engineHours")
entity1.setValue(locationOfMachine.text, forKey:"location")
print(entity1.valueForKey("product"))
print(entity1.valueForKey("modelName"))
print(entity1.valueForKey("serialNo"))
print(entity1.valueForKey("yom"))
print(entity1.valueForKey("engineHours"))
I also get "(lldb)" in the debugger window.
I'll just show you how to get int from string. Use it accordingly:
var aString = "0000" // var aString = textField.text!
var numFromString = Int(aString)
You can assign the text field to aString and convert it to Int like i showed you.
For things that don't need arithmetic, define them as strings in Core Data. For other numbers, it should work to do as you have with Int(YOM.text!).
However, I suggest that you create a managed object subclass for "UsedInfo" so that you can work directly with its properties instead of using setValue:forKey:. The benefit of a subclass is that it will show you data types explicitly.
Validate all textfields before trying to store,set the appropriate keyboard for each textfield and provide the valid character set for each textfield.
For Example:
YOM text field : Use Keyboard with only integers.
Valid character set are 0 to 9
And validation for min and max if applicable.
If any of the validation criteria fails ,throw an alert to input valid data.
I guess this solves your issue.
Xcode 6 has had a ton of bugs. But I'm not quite sure if this is a bug or not. It might not be since this is something I'm just now learning.
My issue is, any time I try to instantiate my subclass of NSManagedObject, I do not have the option to pass the entity: NSEntityDescription and NSManagedContext: insertIntoManagedContext argument to the constructor, Xcode says "Extra Argument 'entity' in call"
I created a new Xcode project from scratch, just to see if I could re-create the problem in a smaller, minimal project.
ToDoList.Item is set as the Item entity class in the Data Model Inspector.
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let ent = NSEntityDescription.entityForName("Item", inManagedObjectContext: context)!
//compiler complains here
var item = Item(entity: ent, insertIntoManagedObjectContext: context)!
}
Here's the subclass:
import UIKit
import CoreData
class Item: NSManagedObject {
#NSManaged var title: String
#NSManaged var completed: Bool
}
All help is appreciated.
Just came across the same problem: Init method for core data entity not available
Obviously we have to implement the
init(entity: NSEntityDescription, insertIntoManagedObjectContext context, NSManagedObjectContext?)
method in our custom NSManagedObject class. So just add
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
to your entity class and it will work.
Try the final line without exclamation mark, like this:
var item = Item(entity: ent, insertIntoManagedObjectContext: context)
And maybe You haven't added your app name to class name:
Swift classes are namespaced—they’re scoped to the module (typically, the project) they are compiled in. To use a Swift subclass of the NSManagedObject class with your Core Data model, prefix the class name in the Class field in the model entity inspector with the name of your module.
https://developer.apple.com/library/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html
Are constructors inherited in Swift?
I'd try using NSEntityDescription.insertNewObjectForEntityForName:inManagedObjectContext