I am using Swift with Realm to build an unidirectional data flow App.
I am wondering why I can not use an object as current application state.
var people is always updated when I add new person but var oldestPerson is never updated.
This is my Store.swift file
class Person: Object {
dynamic var name: String = ""
dynamic var age: Int = 0
}
// MARK: Application/View state
extension Realm {
var people: Results<Person> {
return objects(Person).sorted("age")
}
var oldestPerson: Person? {
return objects(Person).sorted("age").first
}
}
// MARK: Actions
extension Realm {
func addPerson(name: String, age: Int) {
do {
try write {
let person = Person()
person.name = name
person.age = age
add(person)
}
} catch {
print("Add Person action failed: \(error)")
}
}
}
let store = try! Realm()
The state of oldest in my view layer never change but people change as it should.
import RealmSwift
class PersonsTableViewController: UITableViewController {
var notificationToken: NotificationToken?
var people = store.people
var oldest = store.oldestPerson
override func viewDidLoad() {
super.viewDidLoad()
updateView()
notificationToken = store.addNotificationBlock { [weak self] (_) in
self?.updateView()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PersonTableViewCell") as! PersonTableViewCell
cell.person = people[indexPath.row]
return cell
}
func updateView() {
print(oldest)
tableView.reloadData()
}
}
Change your declaration of
var oldest = store.oldestPerson
to:
var oldest: Person? { return store.oldestPerson }
Related
I'm struggle with following challenge. I created table view with custom cell that contains switch. I wanna only one switch can be on i.e, for instance after launch I switched on 3rd switched and then I switched on 7th switch and thus the 3rd one is switched off and so on. I use rx + protocols for cell and don't understand all the way how to determine which switch was toggled. Previously I was going to use filter or map to look up in dataSource array which switch is on and somehow handle this, but now I messed up with it. I'm not sure it's possible without using table view delegate methods. Thanks a lot, hope someone could explain where I am wrong.
//My cell looks like this:
// CellViewModel implementation
import Foundation
import RxSwift
protocol ViewModelProtocol {
var bag:DisposeBag {get set}
func dispose()
}
class ViewModel:ViewModelProtocol {
var bag = DisposeBag()
func dispose() {
self.bag = DisposeBag()
}
}
protocol CellViewModelProtocol:ViewModelProtocol {
var isSwitchOn:BehaviorSubject<Bool> {get set}
}
class CellVM:ViewModel, CellViewModelProtocol {
var isSwitchOn: BehaviorSubject<BooleanLiteralType> = BehaviorSubject(value: false)
let internalBag = DisposeBag()
override init() {
}
}
//My Cell implementation
import UIKit
import RxSwift
import RxCocoa
class Cell:UITableViewCell {
static let identifier = "cell"
#IBOutlet weak var stateSwitch:UISwitch!
var vm:CellViewModelProtocol? {
didSet {
oldValue?.dispose()
self.bindUI()
}
}
var currentTag:Int?
var bag = DisposeBag()
override func awakeFromNib() {
super.awakeFromNib()
self.bindUI()
}
override func prepareForReuse() {
super.prepareForReuse()
self.bag = DisposeBag()
}
private func bindUI() {
guard let vm = self.vm else { return }
self.stateSwitch.rx.controlEvent(.valueChanged).withLatestFrom(self.stateSwitch.rx.value).observeOn(MainScheduler.asyncInstance).bind(to: vm.isSwitchOn).disposed(by: vm.bag)
}
}
//TableViewController implementation
import UIKit
import RxSwift
import RxCocoa
class TableViewController: UITableViewController {
private var dataSource:[CellViewModelProtocol] = []
var vm = TableViewControllerVM()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 70
self.tableView.rowHeight = UITableView.automaticDimension
self.bindUI()
}
private func bindUI() {
vm.dataSource.observeOn(MainScheduler.asyncInstance).bind { [weak self] (dataSource) in
self?.dataSource = dataSource
self?.tableView.reloadData()
}.disposed(by: vm.bag)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier, for: indexPath) as! Cell
if cell.vm == nil {
cell.vm = CellVM()
}
return cell
}
}
class TableViewControllerVM:ViewModel {
var dataSource:BehaviorSubject<[CellViewModelProtocol]> = BehaviorSubject(value: [])
let internalBag = DisposeBag()
override init() {
super.init()
dataSource.onNext(createDataSourceOf(size: 7))
self.handleState()
}
private func createDataSourceOf(size:Int) -> [CellViewModelProtocol] {
var arr:[CellViewModelProtocol] = []
for _ in 0..<size {
let cell = CellVM()
arr.append(cell)
}
return arr
}
private func handleState() {
}
}
Maybe this code will help you:
extension TableViewController {
// called from viewDidLoad
func bind() {
let cells = (0..<7).map { _ in UUID() } // each cell needs an ID
let active = ReplaySubject<UUID>.create(bufferSize: 1) // tracks which is the currently active cell by ID
Observable.just(cells) // wrap the array in an Observable
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: Cell.self)) { _, element, cell in
// this subscription causes the inactive cells to turn off
active
.map { $0 == element }
.bind(to: cell.toggleSwitch.rx.isOn)
.disposed(by: cell.disposeBag)
// this subscription watches for when a cell is set to on.
cell.toggleSwitch.rx.isOn
.filter { $0 }
.map { _ in element }
.bind(to: active)
.disposed(by: cell.disposeBag)
}
.disposed(by: disposeBag)
}
}
Have a similar UI,so tested locally and it works.But not very neat code.
ProfileCellViewModel
struct ProfileCellViewModel {
// IMPORTANT!!!
var bibindRelay: BehaviorRelay<Bool>?
}
ProfileCell
final class ProfileCell: TableViewCell {
#IBOutlet weak var topLabel: Label!
#IBOutlet weak var centerLabel: Label!
#IBOutlet weak var bottomLabel: Label!
#IBOutlet weak var onSwitch: Switch!
public var vm: ProfileCellViewModel? {
didSet {
// IMPORTANT!!!
if let behaviorRelay = vm?.bibindRelay {
(onSwitch.rx.controlProperty(editingEvents: .valueChanged,
getter: { $0.isOn }) { $0.isOn = $1 } <-> behaviorRelay)
.disposed(by: self.rx.reuseBag)
}
}
}
}
ProfileViewModel
final class ProfileViewModel: ViewModel, ViewModelType {
struct Input {
let loadUserProfileStarted: BehaviorRelay<Void>
}
struct Output {
let userItems: BehaviorRelay<[ProfileCellViewModel]>
let chatRelay: BehaviorRelay<Bool>
let callRelay: BehaviorRelay<Bool>
}
let input = Input(loadUserProfileStarted: BehaviorRelay<Void>(value: ()))
let output = Output(userItems: BehaviorRelay<[ProfileCellViewModel]>(value: []),
chatRelay: BehaviorRelay<Bool>(value: false),
callRelay: BehaviorRelay<Bool>(value:false))
override init() {
super.init()
// IMPORTANT!!!
Observable.combineLatest(output.chatRelay,output.callRelay).pairwise().map { (arg0) -> Int in
let (pre, curr) = arg0
let preFlag = [pre.0,pre.1].filter { $0 == true }.count == 1
let currFlag = [curr.0,curr.1].filter { $0 == true }.count == 2
if preFlag && currFlag {
return [pre.0,pre.1].firstIndex(of: true) ?? 0
}
return -1
}.filter {$0 >= 0}.subscribe(onNext: { (value) in
[self.output.chatRelay,self.output.callRelay][value].accept(false)
}).disposed(by: disposeBag)
}
private func createProfileCellItems(user: User) -> [ProfileCellViewModel] {
// IMPORTANT!!!
let chatCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.chat(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.chatRelay)
// IMPORTANT!!!
let callCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.call(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.callRelay)
return [roleCellViewModel,
teamCellViewModel,
statusCellViewModel,
sinceCellViewModel,
chatCellViewModel,
callCellViewModel]
}
}
I mark the codes you should pay attention to with // IMPORTANT!!!
I am trying to use RealmSwift in order to save items to the phone storage in Swift 4. I have two different Views; one for the save functionality and another which will display all saved items into a TableView. I have a buildable form coded but i am throwing an error Thread 1: signal SIGABRT specifically on the line when i call realm.add. When i am in my view which is saving, i am using a IBAction with a button to initiate the save functionality. Can anyone help me with this issue? I think the issue is when i set the var of realm however i am unsure.
UPDATE:
I have changed my implementation to reflect the idea given in this thread about my original issue. After doing so, when the call to add the item to the realm is called i crash EXC_BAD_ACCESS (code=EXC_I386_GPFLT) inside the source code of the API. Specifically I crash at this function of the API
//CODE EXCERPT FROM REALMSWIFT API
// Property value from an instance of this object type
id value;
if ([obj isKindOfClass:_info.rlmObjectSchema.objectClass] &&
prop.swiftIvar) {
if (prop.array) {
return static_cast<RLMListBase *>(object_getIvar(obj,
prop.swiftIvar))._rlmArray;
}
else { // optional
value = static_cast<RLMOptionalBase *>(object_getIvar(obj,
prop.swiftIvar)).underlyingValue; //CRASH OCCURS HERE!!!!!!!!
}
}
else {
// Property value from some object that's KVC-compatible
value = RLMValidatedValueForProperty(obj, [obj
respondsToSelector:prop.getterSel] ? prop.getterName : prop.name,
_info.rlmObjectSchema.className);
}
return value ?: NSNull.null;
import UIKit
import RealmSwift
class DetailsViewController: UIViewController {
var titleOfBook: String?
var author: String?
#IBAction func SavetoFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem) // Crashes on this line
}
}
}
import UIKit
import RealmSwift
final class Favorites: Object {
var title: String?
var author: String?
}
class FavoritesTableViewController: UITableViewController {
var items: Array<Favorites> = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier:
"cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
override func tableView(_ tableView: UITableView?,
numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.author
return cell
}
var selectedIndexPath: NSIndexPath = NSIndexPath()
override func tableView(_ tableView: UITableView, willSelectRowAt
indexPath: IndexPath) -> IndexPath? {
selectedIndexPath = indexPath as NSIndexPath
return indexPath
}
You have to wrap realm.add(newItem) inside a transaction:
try! realm.write {
realm.add(newItem)
}
Please note, that write transactions block the thread they are made on so if you're writing big portions of data you should do so on background thread (realm has to be instantiated on that thread too). You could do it like this:
#IBAction func saveToFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
// avoid force unwrap, optionally report an error
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem)
}
}
}
Update: I haven't noticed that you have an issue with your model too – since Realm is written with Objective C you should mark your model properties with #objc dynamic modifiers:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}
All changes to Realm managed objects (either creation, modification or deletion) need to happen inside write transactions.
do {
try realm.write {
realm.add(newItem)
}
} catch {
//handle error
print(error)
}
For more information, have a look at the writes section of the official docs.
Another problem you have in there is that in your Favorites class properties are missing #objc dynamic attributes. You can read about why you need that in realm docs.
Your code should look like this then:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}
I have the following array which i am getting from Response:
How to get the value of operatingDay for each id in a single string separated by commas eg: Mon,Tue,Wed.... Currently I am getting the cashPointsOperatingDays in an array as a whole. How can I write the Swift3 code?
Using Object Mapper:
// MARK: Travel Shops Mapper
class GetTravelShopsResponse: Mappable {
var message: String?
var status: Int?
var travelShopsData: [TravelShopsResponse]?
var cashCollectionsDateTimeData: [CashCollectionsDateTime]?
// var threeDayForecast: [TravelShopsResponse]?
required init?(map: Map) {
mapping(map: map)
}
func mapping(map: Map) {
message <- map["messages"]
status <- map["status"]
travelShopsData <- map["data"]
}
}
class TravelShopsResponse: Mappable {
var messages: String?
var name: String?
var address: String?
var phoneNumber: String?
var latitude: Float?
var longitude: Float?
var cashPointOperatingDays: [Any]?
var locationid: String?
required init?(map: Map) {
mapping(map: map)
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
address <- map["address"]
phoneNumber <- map["phoneNumber"]
latitude <- map["latitude"]
longitude <- map["longitude"]
cashPointOperatingDays <- map["cashPointOperatingDays"]
locationid <- map["id"]
}
}
In my view Controller:
//
import UIKit
import AlamofireObjectMapper
import Alamofire
class VPContactUsTravelShopsController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var travelShopsTableView: UITableView!
#IBOutlet weak var showallTravelShopsLabel: UILabel!
var yourArray = [Any]()
var nameArray = [Any]()
var addressArray = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
getTravelShopsApiCall(URL:travelShopsURL!as URL)
}
// MARK: - UITableView delegates and datasources
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "location", for: indexPath) as? TravelShopCustomCell {
cell.travelShopName.text = self.nameArray[indexPath.row] as? String
cell.addressTextView.text = self.addressArray[indexPath.row] as? String
//var cashPointArray = cashPointOperatingDays["indexPath.item"]
return cell
} else {
fatalError("Dequeueing SomeCell failed")
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 480
}
// MARK: - Get Contact Us API call
func getTravelShopsApiCall(URL: URL) {
Alamofire.request(URL, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
.responseObject { (response: DataResponse<GetTravelShopsResponse>) in
print(response.result.value!)
switch response.result {
case .success:
if !(response is NSNull) {
// optional is NOT NULL, neither NIL nor NSNull
guard let end = response.result.value else {
return
}
DispatchQueue.main.async {
//end = nullToNil(end.loginAuthenticationInfo?.accessToken)
self.yourArray = end.travelShopsData!
var dataArray = [Any]()
print(self.yourArray)
if let threeDayForecast = end.travelShopsData {
for forecast in threeDayForecast {
self.nameArray.append(forecast.name as Any)
self.addressArray.append(forecast.address as Any)
dataArray.append(forecast.cashPointOperatingDays as Any)
print(forecast.name as Any)
print(forecast.address as Any)
print(forecast.phoneNumber as Any)
print(forecast.latitude as Any)
print(forecast.longitude as Any)
print(forecast.cashPointOperatingDays as Any)
}
}
// if let cashCollectionsDateTime = end.cashCollectionsDateTimeData {
// for operationDetails in cashCollectionsDateTime {
// self.cashPointOperatingDaysArray.append(operationDetails.day as Any)
//
// }
// }
self.travelShopsTableView.reloadData()
}
} else {
// null
}
break
case .failure:
if let error = response.result.error as? URLError {
print("URLError occurred: \(error)")
} else {
print("Unknown error: \(String(describing: response.result.error))")
}
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
I have not tested this code on XCode or playground, so mind checking it.
I think this code snippet can be used in appending the array of cashPointOperatingDays.
for cashPointOperatingDay in cashPointOperatingDays {
cashPointOperatingDays.append(cashPointOperatingDay)
}
This should be in your TravelShopsResponse class beneath the declaration of
cashPointOperatingDays <- map["cashPointOperatingDays"]
And for retrieving, we can use in a tableView or collectionView cellForRow method:
var cashPointArray = cashPointOperatingDays["indexPath.item"]
let retrieveArray = cashPointArray["operatingDay"]
print(retrieveArray)
Again, please check it in your code.
I have been trying to set up, what seems like it should be, a simple app that allows a user to update a food item with a price and store where the price was found. The main issue I know is trying to blend Swift with Objective-C even though Apple hasn't worked out the kinks yet for Swift and it's ever changing.
Anyways I have set up my AllowedTableViewController as follows
class AllowedTableViewController: UITableViewController {
var myAllowedList : Array<AnyObject> = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "FoodAllowed")
myAllowedList = context.executeFetchRequest(freq, error: nil)!
tableView.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "update" {
var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!
var selectedItem: NSManagedObject = myAllowedList[indexPath.row] as NSManagedObject
let IVC: AllowedViewController = segue.destinationViewController as AllowedViewController
IVC.name = selectedItem.valueForKey("name")as String
IVC.store = selectedItem.valueForKey("store")as String
IVC.price = selectedItem.valueForKey("price")as String
IVC.existingItem = selectedItem
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return myAllowedList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Configure the Cell
let CellID: NSString = "Allowed"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
var data: NSManagedObject = myAllowedList[indexPath.row] as NSManagedObject
cell.textLabel.text = (data.valueForKeyPath("name")as String)
var pri = data.valueForKeyPath("price")as String
var str = data.valueForKeyPath("store")as String
cell.detailTextLabel?.text = "\(pri) name/s - \(str)"
return cell
}
//Override to support conditional editing of the table view
override func tableView(tableView: UITableView?, canEditRowAtIndexPath indexPath: NSIndexPath?) -> Bool {
//Return NO if you do not want the specified item to be editable
return true
}
//Override to support editing the table view
override func tableView(tableView: UITableView?, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath?) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
if editingStyle == UITableViewCellEditingStyle.Delete {
if let tv = tableView {
context.deleteObject(myAllowedList[indexPath!.row] as NSManagedObject)
myAllowedList.removeAtIndex(indexPath!.row)
tv.deleteRowsAtIndexPaths([indexPath!.row], withRowAnimation: UITableViewRowAnimation.Fade)
}
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
}
My user editable text field code in the view controller are as follows.
{
class AllowedViewController: UIViewController {
#IBOutlet var textFieldname: UITextField!
#IBOutlet var textFieldstore: UITextField!
#IBOutlet var textFieldprice: UITextField!
var name: String = ""
var store: String = ""
var price: String = ""
var existingItem: NSManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
if existingItem == nil {
textFieldname.text = name
textFieldstore.text = store
textFieldprice.text = price
}
// Do any additional setup after loading the view.
}
func saveTapped(sender: AnyObject) {
//Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
//Reference NS Managed Object Context
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("FoodAllowed", inManagedObjectContext: contxt)!
//Check if item exists
if (existingItem != nil) {
existingItem.setValue(textFieldname.text as String, forKey: "name")
existingItem.setValue(textFieldstore.text as String, forKey: "store")
existingItem.setValue(textFieldprice.text as String, forKey: "price")
}else {
//Create instance of pur data model and intialize
var newItem = DataModel(entity: en, insertIntoManagedObjectContext: contxt)
//Map our properties
newItem.name = [textFieldname.text]
newItem.store = [textFieldstore.text]
newItem.price = [textFieldprice.text]
//Save our content
contxt.save(nil)
println(newItem)
//Navigate back to root view controll
self.navigationController?.popToRootViewControllerAnimated(true)
}
}
func cancelTapped(sender: AnyObject) {
//Navigate back to root view controll
self.navigationController?.popToRootViewControllerAnimated(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
}
My NSManaged DataModel is
{
#objc(DataModel)
class DataModel: NSManagedObject {
//properties feeding the attributes in our entity
//must match the entity attributes
#NSManaged var name: [String]
#NSManaged var store: [String]
#NSManaged var price: [String]
}
}
When I run the app in the simulator I get the following error
'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "name"; desired type = NSString; given type = Swift._NSSwiftArrayImpl; value = (Apples).'
What am I doing wrong?
The error message indicates that "name", "store" and "price" are String properties
of your Core Data entity, but you have defined them as [String], i.e. an array
of strings. It should be
#objc(DataModel)
class DataModel: NSManagedObject {
#NSManaged var name: String
#NSManaged var store: String
#NSManaged var price: String
}
And consequently
newItem.name = textFieldname.text // not: [textFieldname.text]
// ...
Better yet, let Xcode generate the managed object subclasses
(Editor -> Create NSManagedObject Subclass ... in the Xcode menu).
How would I go about implementing a friends relationship between users, using Firebase in swift?
The following is my current DataService.swift file.
import Foundation
import Firebase
let DB_BASE = Database.database().reference()
class DataService {
// creates instance of the class inside itself
static let instance = DataService()
private var _REF_BASE = DB_BASE
private var _REF_USERS = DB_BASE.child("users")
var REF_BASE: DatabaseReference {
return _REF_BASE
}
var REF_USERS: DatabaseReference {
return _REF_USERS
}
func createDBUser(uid: String, userData: Dictionary<String,Any>) {
REF_USERS.child(uid).updateChildValues(userData)
}
}
Thanks :)
Not sure this is a very complete answer but may get you going in the right direction
Assume we have three users in Firebase and there is data is stored in a /users node. Larry (uid_0) has two friends, Moe and Curly
users
uid_0
name: "Larry"
friends:
uid_1: true
uid_2: true
uid_1
name: "Moe"
uid_2
name: "Curly"
The code to write the friends node is something like this
let usersRef = fbRed.child("users")
let uid0Ref = usersRef.child("uid_0")
let friendsRef = uid0Ref.child("friends")
friendsRef.child("uid_1").setValue(true)
friendsRef.child("uid_2").setValue(true)
That's pretty verbose and could be condensed but leaving that way for readability.
Note that if you are going to do queries or other functions in the future, this is probably not the best solution.
Each user should have a list of references to other users via their uid. Do not store the entire friend user data object under each user. This will greatly inflate your database.
users ->
"uid1" ->
friendUids ->
0 ->
"uid2"
1 ->
"uid3"
"uid2" ->
friendUids ->
0 ->
"uid1"
"uid3" ->
friendUids ->
0 ->
"uid1"
Assuming you have a static decode function on your user to handle the JSON from Firebase, all you need to do to retrieve the user data is:
extension DataService {
func requestUserWithUid(_ uid: String, completion: #escaping (User?) -> Void) {
REF_USERS.child(uid).observeSingleEvent(of: .value) { snapshot in
completion(User.decode(snapshot.value))
}
}
func requestFriendsForCurrentUser(completion: #escaping ([User?]?) -> Void) {
var friends = [User?]()
guard let uid = Auth.auth().currentUser?.uid else {
completion(.none)
return
}
requestUserWithUid(uid) { user in
guard let user = user else {
completion(.none)
return
}
for friendUid in user.friendUids {
requestUserWithUid(friendUid) { friend in
friends.append(friend)
}
}
completion(friends)
}
}
}
This is just limited example of many of how you could implement it. Let's assume your user data object has a name and we want to display a list of friends' names.
class FriendView: UIView, UITableViewDataSource {
fileprivate var friends = [User?]()
fileprivate let tableView = UITableView(frame: .zero, style: .plain)
// ----------------------------------------------------------------------------
// MARK: UIView
// ----------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(tableView)
tableView.frame = frame
tableView.dataSource = self
loadFriends()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder :) has not been implemented")
}
// ----------------------------------------------------------------------------
// MARK: Private
// ----------------------------------------------------------------------------
fileprivate func loadFriends() {
DataService.instance.requestFriendsForCurrentUser() { friends in
if let friends = friends {
self.friends = friends
self.tableView.reloadData()
}
}
}
// ----------------------------------------------------------------------------
// MARK: UITableViewDataSource
// ----------------------------------------------------------------------------
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = friends[indexPath.row]?.name
return cell
}
}