I'm trying to get my head around optionals, let's say I have the following classes:
class Contact {
var displayName:String
init(displayName:String) {
self.displayName = displayName
}
}
class Contacts {
func create(displayName:String) -> Contact {
return Contact(displayName: displayName)
}
}
as you can see the Contact class has a field displayName, but this value can be nil on initialization.
so to initialize this class I would normally do:
let contact = Contact(displayName: "Test Name")
but instead I want to be able to do this as well:
let contact = Contact()
or
let contact = Contacts().create()
You can make the init parameter an optional String?, with a default
value nil:
class Contact {
var displayName: String?
init(displayName: String? = nil) {
self.displayName = displayName
}
}
let contact1 = Contact()
let contact2 = Contact(displayName: "John")
The same works for the Contacts class:
class Contacts {
func create(displayName: String? = nil) -> Contact {
return Contact(displayName: displayName)
}
}
let contacts = Contacts()
let contact3 = contacts.create()
let contact4 = contacts.create("Mary")
Change your class to this if you want to use an optional displayName:
class Contact {
var displayName: String?
convenience init(displayName: String) {
self.init()
self.displayName = displayName
}
}
This allows you to to this:
let contact = Contact()
let otherContact = Contact(displayName: "Test Name")
EDIT:
Here's the create function as well:
class Contacts {
func create() -> Contact {
return Contact()
}
func create(displayName: String) -> Contact {
return Contact(displayName: displayName)
}
}
But I'd recommend this:
class Contact {
var displayName: String?
init(displayName: String?) {
self.displayName = displayName
}
}
class Contacts {
func create(displayName: String?) -> Contact {
return Contact(displayName: displayName)
}
}
Made the displayName property optional, like so:
var displayName:String?
Optionals can either hold nil or an actual value, in your case a string.
Related
I'm very new to Parse and Swift and I have this project I am working on and I am trying to create a search bar that displays all the items from the key "names" from my Parse database.
I have created this function that is supposed to take all the names and return them in a string array. But instead, the array never gets filled and all I get as a return is [].
class Offices {
var name: String
var phone: String
var location: String
init(name: String = "def_name", phone: String = "def_phone", location: String = "def_location") {
self.name = name
self.phone = phone
self.location = location
}
func retrieveName() -> [String] {
var models = [String]()
let queries = PFQuery(className: "Directory")
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
models.append(i["name"] as? String ?? self.name)
}
} else {
// The query succeeded but no matching result was found
}
}
return models
}
findObjectsInBackground method is asynchronous. So you should change retrieveName function as below:
class Offices {
var name: String
var phone: String
var location: String
init(name: String = "def_name", phone: String = "def_phone", location: String = "def_location") {
self.name = name
self.phone = phone
self.location = location
// I call retrieveName here for example. You can call it where you want.
retrieveName() { (success, models) in
if success {
print(models)
} else {
print("unsuceess")
}
}
}
func retrieveName(completion: #escaping (_ success: Bool, _ models: [String]) -> Void) {
var models = [String]()
let queries = PFQuery(className: "Directory")
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
completion(false, [])
} else if let object = object {
// The query succeeded with a matching result
for i in object{
models.append(i["name"] as? String ?? self.name)
}
completion(true, models)
} else {
completion(true, [])
// The query succeeded but no matching result was found
}
}
}
}
Using Swift 5 for iOS13.
I am trying to update an existing Realm record with a Contact Picker result. The function deletes all the object content except for the new content.
My code
class Person: Object {
#objc dynamic var personId = UUID().uuidString
#objc dynamic var firstName: String = ""
#objc dynamic var surname: String = ""
#objc dynamic var mobileNumber: Int = 0
#objc dynamic var password: String = ""
#objc dynamic var myContactID: String = ""
override static func primaryKey() -> String? {
return "personId"
}
}
extension HomeController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
picker.dismiss(animated: true, completion: nil)
let person = Person()
let me = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber)
person.myContactID = contact.identifier
person.personId = me.first!.personId
try! realm.write {
realm.add(person, update: .modified)
}
self.viewWillAppear(true)
}
}
All the existing content of the Person class in the Realm database disappears except for myContactID and personID.
That is because you are updating the Person with new data.
The line let person = Person() creates a new instance with all the default values. (firstName: String = "" etc..)
So, when you assign myContactID and personId to this newly created person, it will look like this:
Person {
personId = FAE4C224-D37E-4C77-B6F1-C60A92F188D0;
firstName = ;
surname = ;
mobileNumber = ;
password = ;
myContactID = contactIdentifier;
}
And when you call realm.add(person, update: .modified), it will overwrite the record associated with the primary key with this newly created person.
You want to fetch an existing person and modify it. You can do something like this:
guard let me = realm.objects(Person.self).filter("mobileNumber == %#", mobileNumber).first else { return }
try! realm.write {
me.myContactID = contact.identifier
}
Initially I had the following classes:
#objcMembers public class NormalObjectRealm: Object {
// Shared
dynamic public var id: String?
dynamic public var title: String?
dynamic public var subTitle: String?
dynamic public var imageInfo: ImageInfoRealm?
dynamic public var descriptionString: String?
public var categories = List<String>()
public var count = RealmOptional<Int>()
public var episodes = List<String>()
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return NormalObjectRealm.realmPrimaryKey
}
}
#objcMembers public class ImageInfoRealm: Object {
dynamic public var id: String?
dynamic public var url: String?
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return ImageInfoRealm.realmPrimaryKey
}
}
but now NormalObjectRealm is kind of incorporated into a new class like so:
#objcMembers public class MediaObjectRealm: Object {
// Shared
dynamic public var id: String?
dynamic public var title: String?
dynamic public var subTitle: String?
dynamic public var imageInfo: ImageInfoRealm?
dynamic public var descriptionString: String?
public var categories = List<String>()
dynamic public var type: String?
// NormalObjectRealm
public var episodeCount = RealmOptional<Int>()
public var episodes = List<String>()
// OtherObjectRealm
dynamic public var urlOne: String?
dynamic public var urlTwo: String?
dynamic public var urlThree: String?
public var isExplicit = RealmOptional<Bool>()
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return MediaObjectRealm.realmPrimaryKey
}
}
I'm currently trying to write the migration for the transition here where the idea basically is to transfer most of the fields over from NormalObjectRealm to MediaObjectRealm.
This is what my migration-block currently looks like
Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < temp {
print("RealmMigration: Applying migration from \(oldSchemaVersion) to \(temp)")
migration.enumerateObjects(ofType: "NormalObjectRealm") { oldObject, newObject in
guard let oldObject = oldObject else {
return
}
guard let id = oldObject["id"] as? String else {
return
}
guard let title = oldObject["title"] as? String else {
return
}
guard let subTitle = oldObject["subTitle"] as? String else {
return
}
guard let imgInfo = oldObject["imageInfo"] else {
return
}
guard let count = oldObject["count"] as? RealmOptional<Int>? else {
return
}
guard let descriptionString = oldObject["descriptionString"] as? String? else {
return
}
let item = migration.create("MediaObjectRealm")
item["id"] = id
item["title"] = title
item["subTitle"] = subTitle
item["descriptionString"] = descriptionString
item["type"] = "myType"
item["episodeCount"] = episodeCount // Doesn't work either...
migration.enumerateObjects(ofType: "ImageInfoRealm") { oldImg, newImg in
guard let oldImg = oldImg else {
return
}
let inf = oldObject.value(forKey: "imageInfo")
print(inf)
let t = migration.create("ImageInfoRealm", value: inf)
print("doing it")
// print(t)
item.setValue(t, forKey: "imageInfo")
}
}
}
})
id, title, subTitle etc. (String? and Date? variables) are set fine and appear inside the newly created MediaObjectRealm DB-Entries. However imageInfo of type ImageInfoRealm does not... setting it directly like so: item.setValue(oldObject.value(forKey: "imageInfo"), forKey: "imageInfo") (or item["imageInfo"] = oldObject.value(forKey: "imageInfo")) results in realm crashing and telling me that this object is from another realm and I have to copy it over.
'Object is already managed by another Realm. Use create instead to
copy it into this Realm.'
Creating it like in the code above results in not even having any items of type MediaObjectRealm at all i.e. loosing all the data (as NormalObjectRealm is also not present anymore).
Am I missing something? What I basically want is to to take the link/reference from the NormalObjectRealm and copy it to the new MediaObjectRealm.
After long testing and trying different possibilities I managed to migrate the data.
Here is what I did to accomplish this.
I used this as a base:
class RealmMigrationObject {
let migration: () -> ()
init(migration: #escaping () -> ()) {
self.migration = migration
}
}
and derived classes from that. Something like:
class MigrationObjectToThree: RealmMigrationObject {
init() {
super.init(migration: MigrationObjectToThree.migration)
}
private static func migration() {
print("Migration to three | migration")
var imageInfos: [ImageInfo] = []
let config = Realm.Configuration(schemaVersion: 3, migrationBlock: { migration, oldSchemaVersion in
print("Migration to three | migrationBlock")
print("RealmMigration: Applying migration from \(oldSchemaVersion) to 3")
migration.deleteData(forType: "ExploreSectionObjectRealm")
migration.enumerateObjects(ofType: "ImageInfoRealm") { oldInfo, newObject in
guard let oldInfo = oldInfo else {
return
}
guard let id = oldInfo["id"] as? String,
let url = oldInfo["url"] as? String,
let url500 = oldInfo["url500"] as? String,
let url400 = oldInfo["url400"] as? String,
let url300 = oldInfo["url300"] as? String,
let url200 = oldInfo["url200"] as? String,
let url100 = oldInfo["url100"] as? String,
let colorString = oldInfo["color"] as? String,
let color = UIColor(hexString: colorString) else {
return
}
imageInfos.append(ImageInfo(id: id,
url: url,
url500: url500,
url400: url400,
url300: url300,
url200: url200,
url100: url100,
color: color))
}
})
Realm.Configuration.defaultConfiguration = config
do {
let realm = try Realm(configuration: config)
print("Realm is located at: \(realm.configuration.fileURL?.description ?? "")")
print(realm.configuration.fileURL?.description ?? "") // Printing here on purpose as it's easier to copy
} catch {
print("Realm Error: \(error), trying to rebuild realm from scratch")
let deleteMigrationConfig = Realm.Configuration(schemaVersion: RealmHelper.schemaVersion,
deleteRealmIfMigrationNeeded: true)
do {
_ = try Realm(configuration: deleteMigrationConfig)
} catch {
print("Failed to instantiate: \(error.localizedDescription)")
}
}
RealmHelper.removeRealmFiles()
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 3)
imageInfos.forEach({ $0.save() })
}
}
From that I just created all migration for the difference between the current schema version and target schema version on looped over all migrations simply executing the migration function of that given object.
I want to refactor my code to apply clean approach.
I have a class user
class User {
let name: String?
let id: String
var isOnline: Bool
var mesaageHistory = [Message]()
init(name: String, id: String, isOnlineStatus: Bool) {
self.name = name
self.id = id
self.isOnline = isOnlineStatus
}
}
Then I'm using fabric pattern to create list of my users.
protocol CreateUserProtocol: class {
func sortArray(inputArr: [User])
}
class CreateUserEntity: CreateUserProtocol {
static let shared = CreateUserEntity()
var users = [User]()
func sortArray(inputArr: [User]){
var applySortingByDate: Bool = false
for user in inputArr {
if !user.mesaageHistory.isEmpty {
applySortingByDate = true
}
}
if applySortingByDate {
inputArr.sorted(by: { (first, second) -> Bool in
(first.mesaageHistory.last?.messageTime)! < (second.mesaageHistory.last?.messageTime)!
})
} else {
inputArr.sorted(by: { (first, second) -> Bool in
first.name! < second.name!
})
}
}
}
One controller is responsible for appending new users, while another is used to retrieve them and bind them to tableView. Everything is working fine, but I think my solution is not good enough for scaling.
Moreover in one of my VC I use to sort my Users to online and offline. I think, that I shouldn't do that in my VC and to put this logic into my CreateUserEntity
var onlineUsersData = [User]()
var offlineUsersData = [User]()
private func classifyUsers() {
for user in CreateUserEntity.shared.users {
print("is user online: \(user.isOnline)")
print(CreateUserEntity.shared.users.count)
if user.isOnline == true && !onlineUsersData.contains(user) {
onlineUsersData.append(user)
}
if user.isOnline == false && !offlineUsersData.contains(user) {
offlineUsersData.append(user)
}
}
}
I'd like to rewrite it in proper way, what can you recommend me?
From my opinion try to use firstly struct instead of the class.
Example:
struct User {
let name: String
}
Then you should figure out where you will be storing these users? Now they are in the memory. So we should define where and how we will be storing them.
So probably for this case we can consider NSUserDefaults as core for the class that will store users.After that we should create facade to this class to manage our users.
protocol UserStoreFacade {
func fetch(name withName:String) -> User
func create(name withName:String) -> User
func save(user:User)
func update(name newName:String) -> User
func delete(name withName:String)
}
And UserStore that is used to manage user.
class UserStore: UserStoreFacade {
let defaults = UserDefaults(suiteName: "User")
func fetch(name withName:String) -> User {
let encodeData = defaults?.dictionary(forKey: withName)
return User(dictionary: encodeData as! Dictionary<String, AnyObject>)
}
func create(name withName: String) -> User {
return User(name: withName)
}
func save(user: User) {
defaults?.set(user.encode(), forKey: user.name)
}
func update(name newName:String) -> User {
return User(name: newName)
}
func delete(name withName:String) {
defaults?.removeObject(forKey: withName)
}
}
It is quite primitive but still showing case how it is possible to accomplish.
I am developing an application with swift and parse.com, but I have problems with the model of the application.
I have an Event class that contains a Category class.
this is my Category class :
class Category : PFObject, PFSubclassing{
override class func load() {
self.registerSubclass()
}
class func parseClassName() -> String! {
return "Category"
}
var nameCategory:String="";
var image:String="";
private let KeyName:String = "label";
private let KeyImage:String = "image";
func fromPFObject(object:PFObject)->Category{
self.nameCategory = object[self.KeyName] as String;
self.image = object[self.KeyImage] as String;
return self;
}
And this is my Event class :
class Event {
//Variables de classe
var name:String;
var category:Category;
// Descripteur de colone en base
private let KeyEventName:String = "event_name";
private let KeyCategory:String = "category";
func insert(){
var event = self.toPFObject()
event.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
if (success != false) {
println("Object created with id: \(event.objectId)")
self.id = event.objectId;
} else {
println(error)
}
}
}
func select(id:String){
var query = PFQuery(className: self.KeyClassName)
query.getObjectInBackgroundWithId(id) {
(event: PFObject!, error: NSError!) -> Void in
if (error == nil) {
self.fromPFObject(event);
} else {
println(error)
self.eventKO("Une erreur c'est produite");
}
}
}
func toPFObject()->PFObject{
var event = PFObject(className: self.KeyClassName)
event[self.KeyCategory] = self.category;
event[self.KeyEventName] = self.name
return event;
}
func fromPFObject(event:PFObject)->Event{
self.activ = event[self.KeyActif] as Int;
var test = event[self.KeyCategory] as PFObject;
var label = test["label"] as String;
self.category = event[self.KeyCategory] as Category;
self.name = event[self.KeyEventName] as String;
return self;
}
I get a good retrieve the name of the event but not the category. I have missed something in my models.
Do you have any ideas?
I found it simpler to work directly with the objects instead of subclassing.
Simply refer to properties on your classes as follows:
// non optional of type String that defaults to an empty string
let firstName = object["firstName"] as? String ?? ""
if you have linked classes, e.g. if Category is another class you have a reference to, the following works cleanly
if let category = object["category"] as? PFObject {
let name = category["name"] as? String ?? ""
}
Again, no need to mess around with subclassing everything for simple objects.
I've used this patter to populate PFQueryTableViewControllers, custom tables, custom views etc.