I've been trying to get started with Realm (version 4.3.0) as a database option with Xcode 11. With my Googling skills I could not get an answer for my problems. I've tried to use the Official Realm documentation but it appears that the way they do things is not working with Xcode 11. Basic code:
import UIKit
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
class Test: Object {
#objc dynamic var text = ""
#objc dynamic var internalId = 0
}
let newTest = Test()
newTest.text = "Text" // Errors happen here
print("text: \(newTest.text)")
}
I get an errors that I definitely was not expecting:
Consecutive declarations on a line must be separated by ';'
Expected '(' in argument list of function declaration
Expected '{' in body of function declaration
Expected 'func' keyword in instance method declaration
Expected declaration
Invalid redeclaration of 'newTest()'
Also when I'm trying to initialize and write to Realm with:
let realm = try! Realm()
try! realm.write { // Error here
realm.add(newTest)
}
I get the error of "Expected declaration"
From what I've read, Realm seems like a really nice database option for iOS, but with these problems, I cannot get up and running. Any help would be greatly appreciated.
Let's rearrange the code so the objects and functions are in their correct place.
import UIKit
import RealmSwift
//this makes the class available throughout the app
class Test: Object {
#objc dynamic var text = ""
#objc dynamic var internalId = 0
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//create a new realm object in memory
let newTest = Test()
newTest.text = "Text"
print("text: \(newTest.text)")
//persist the object to realm
let realm = try! Realm()
try! realm.write {
realm.add(newTest)
}
//or read objects
let results = realm.objects(Test.self)
for object in results {
print(object.text)
}
}
}
Like #108g commented:
I was trying to create an instance at class level. So I moved the creation, modification and the print inside viewDidLoad() method. Then I moved my Test class into a new file.
So the code that works:
ViewController.swift
import UIKit
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let newTest = Prompt()
newTest.text = "Text"
print("text: \(newTest.text)")
let realm = try! Realm()
try! realm.write {
realm.add(newTest)
}
}
}
And RealmTest.swift (new file)
import Foundation
import RealmSwift
class Prompt: Object {
#objc dynamic var text = ""
#objc dynamic var internalId = 0
}
Related
PetInfo.class
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
init(){ .. }
}
ViewController.swift
class ViewController: UIViewController {
var PetArray = PetInfo.shared.petArray
override func viewDidLoad() {
super.viewDidLoad()
let pet = PetInfo()
pet.PetName = "Jack"
PetArray.append(pet). **Create Object and gave a name**
print(PetArray[0].PetName) //works!
}
}
secondViewController.swift
class secondViewController: UIViewController {
var PetArray = PetInfo.shared.petArray
override func viewDidLoad() {
super.viewDidLoad()
let label: UILabel = {
let label = UILabel()
...
label.text = PetArray[0].PetName **tried to print**
return label
}()
view.addSubview(label)
}
}
I want to share PetArray array in all of the view controllers.(It's more than two.)
It put data in the first VC, but doesn't work in the Second VC.
How can I share this array using a Singleton pattern?
Except for the array, It works perfect.(like String.. PetID, PetName.. )
Array in swift is implemented as Struct, which means Array is a value type and not a reference type. Value types in Swift uses copy on write (COW) mechanism to handle the changes to their values.
So in your ViewController when you assigned
var PetArray = PetInfo.shared.petArray
your PetArray was still pointing to the same array in your PetInfo.shared instance (I mean same copy of array in memory) . But as soon as you modified the value of PetArray by using
PetArray.append(pet)
COW kicks in and it creates a new copy of petArray in memory and now your PetArray variable in your ViewController and PetInfo.shared.petArray are no longer pointing to same instances instead they are pointing to two different copies of array in memory.
So all the changes you did by using PetArray.append(pet) is obviously not reflected when you access PetInfo.shared.petArray in secondViewController
What can I do?
remove PetArray.append(pet) and instead use PetInfo.shared.petArray.append(pet)
What are the other issues in my code?
Issue 1:
Never use Pascal casing for variable name var PetArray = PetInfo.shared.petArray instead use camel casing var petArray = PetInfo.shared.petArray
Issue 2:
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
init(){ .. }
}
This implementation will not ensure that there exists only one instance of PetInfo exists in memory (I mean it cant ensure pure singleton pattern), though you provide access to instance of PetInfo using a static variable named shared there is nothing which stops user from creating multiple instances of PetInfo simply by calling PetInfo() as you did in let pet = PetInfo()
rather use private init(){ .. } to prevent others from further creating instances of PetInfo
Issue 3:
You are holding an array of PetInfo inside an instance of PetInfo which is kind of messed up pattern, am not really sure as to what are you trying to accomplish here, if this is really what you wanna do, then probably you can ignore point two (creating private init) for now :)
I think the best solution to use Combine and Resolver frameworks. Works perfectly in my case with shared arrays.
In your case it could be
import Combine
import Resolver // need to add pod 'Resolver' to Podfile and install it first
// Data Model
struct PetInfo: Codable {
var PetID:Int
var PetName:String
...
}
// Repository to read manage data (read/write/search)
class PetRepository: ObservableObject {
#Published var petArray = Array<PetInfo>()
override init() {
super.init()
load()
}
private func load() {
// load pets info from server
}
}
Need to add AppDelegate+Injection.swift that will contain repository registration:
import Foundation
import Resolver
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
// Services
register { PetRepository() }.scope(application)
}
}
Then use it in any controllers
import UIKit
import Combine
import Resolver
class ViewController: UIViewController {
#LazyInjected var petRepository: PetRepository
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
petRepository.$petArray
.receive(on: DispatchQueue.main)
.debounce(for: 0.8, scheduler: RunLoop.main)
.sink { [weak self] petInfos in
// set UI here
}
.store(in: &cancellables)
}
}
If you want PetInfo to be a singleton, make its initializer private:
class PetInfo {
static let shared: PetInfo = PetInfo()
lazy var petArray: [PetInfo] = []
var PetID:Int
var PetName:String
...
private init(){ .. } // !!
}
This way, any attempt to create new instances (like you do in your first ViewController) will fail, and will remind you to always use PetInfo.shared to access the singleton.
tableView ambiguous reference error
I am facing this error like many of you faced already. I have following code in my UITableViewController:
import Foundation
import RxSwift
import RxCocoa
class DiscoveryViewController : UITableViewController {
// MARK: - Properties
let viewModel = MFMovieListViewModel()
let disposeBag: DisposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupBindings()
}
// MARK: - Rx binding
private func setupBindings() {
self.viewModel
.movies
.bind(to: tableView
.rx
.items(cellIdentifier: MovieListCell.DefaultReuseIdentifier,
cellType: MovieListCell.self)) {
(row, movie, cell) in
cell.configure(with: movie)
}.addDisposableTo(self.disposeBag)
}
}
View Model looks like:
import Foundation
import RxSwift
import RxCocoa
class MFMovieListViewModel {
// MARK: - Properties
lazy var movies: Observable<[MovieListMDB]> = {
return MFAPIClinet.sharedInstance().popularMovies()
}()
}
Don't think it's related to Xcode 8.3 or OSX 10.12, but still tried restarting but nothing is resolved. Appreciate any help provided.
Found the issue. DefaultReuseIdentifier wasn't defined in the MovieListCell. :)
Funny error message though!
I'm trying to retrieve data from my Firebase database, but I'm stuck at the database reference returning nil. However, I successfully can retrieve the UserID, so I don't think it's not properly connected to my database:
import UIKit
import Firebase
import FirebaseDatabaseUI
class LoggedInViewController: UIViewController {
var ref: FIRDatabaseReference!
let userid = FIRAuth.auth()?.currentUser?.uid
override func viewDidLoad() {
super.viewDidLoad()
UIDLabel.text = String((userid)!) // The Label shows the UID
reflabel.text = String(ref) // The Label shows "nil"
}
}
My goal is to retrieve data from the path using this method:
func getQuery() -> FIRDatabaseQuery {
let myTopPostsQuery = (ref.child("user-posts")).child(getUid()))
return myTopPostsQuery
}
But obviously this doesn't work since I'm getting an error because of the nil reference.
In viewDidLoad use FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
self.ref = FIRDatabase.database().reference()
//....
}
I've just update my realm to 0.93.2 from 0.91.1 using cocoapods.
I now get empty objects in my query results. So I made a simple app just to test from scratch but I still get the same results.
Here is my test code (basically just one textfield and two buttons (add and print):
import UIKit
import RealmSwift
class Person: Object {
var name = "Empty Value"
}
class ViewController: UIViewController {
#IBOutlet weak var nameTextField: UITextField!
var realm = Realm()
override func viewDidLoad() {
super.viewDidLoad()
println(realm.path)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addTapped(sender: UIButton) {
var person = Person()
person.name = nameTextField.text
realm.write {
self.realm.add(person)
println("Person added: \(person.name)")
}
}
#IBAction func printListTapped(sender: UIButton) {
println("\n\nPeople\n")
for person in realm.objects(Person) {
println("Person: \(person.name)")
}
}
}
The data is saved to the database, as they're seen in the Realm Browser.
But the objects returned by realm.objects(Person) are all empty.
This is the output of the "printListTapped" function after adding 2 items:
People
Person: Empty Value<br/>
Person: Empty Value
I'm really not sure what I'm missing here. Thanks in advance.
The issue here is that your name property is declared without dynamic, so it's completely invisible to Realm. It should work if you declare it as dynamic var name = "Empty Value" instead.
I coding an app with several (15-25 different swigft files one for each view.
Some variables and functions I will use in every viewcontroller.
What would be best practice to enable code reusage?
For instance I need to communicate with a server in which the first request is for an access token, this request I imagine could be a global function setting a global variable (access token). And then using it for the more specific requests.
I started placing a lot of global constants in appdelegate file, in a Struct is there a problem with this?
LibraryAPI.swift
import UIKit
import CoreData
class LibraryAPI: NSObject
{
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
private var loginD: LoginDetails
private var isOnline: Bool
class var sharedInstance: LibraryAPI
{
struct Singleton
{
static let instance = LibraryAPI()
}
return Singleton.instance
}
override init()
{
super.init()
}
func getIsOnline() -> Bool
{
return isOnline
}
func setIsOnline(onlineStatus: Bool)
{
isOnline = onlineStatus
}
func getLoginDetails() -> LoginDetails
{
return loginD
}
func setLoginDetails(logindet: LoginDetails)
{
loginD = logindet
}
// Execute the fetch request, and cast the results to an array of objects
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LoginDetails] {
setLoginDetails(fetchResults[0])
}
}
You should avoid using global variables.
Depending on what you have / what you need to do, either you can :
Create a class and make an instance on your first call. Then, you can pass the object through your views (prepareForSegue). But that can still be repetitive to implement everytime ;
Create a singleton class that will be instantiate only once and accessible from everywhere (singleton are considered as a bad practice by some);
Use the NSUserDefaults to store String ;
Save your data somehow (CoreData, ...).
You can do like this
User.swift
import Foundation
import UIKit
class User: NSObject {
var name: String = ""
func getName() -> String{
name = "Nurdin"
return name
}
}
ViewController.swift
import Foundation
import UIKit
let instanceOfUser = User()
println(instanceOfUser.getName()) // return Nurdin