I'm trying to learn how to use NSTableViewDiffableDataSource to load data with NSTableView. I am able to use UITableViewDiffableDataSource and UICollectionViewDiffableDataSource to load data in iOS because I have found some examples online. But I am not able to use NSTableViewDiffableDataSource in Cocoa.
In the following case, I have a subclass of NSTableCellView named TestTableCellView, which shows three fields: First name, Last name, and his or her date of birth in String.
import Cocoa
class ViewController: NSViewController {
// MARK: - Variables
var dataSource: NSTableViewDiffableDataSource<Int, Contact>?
// MARK: - IBOutlet
#IBOutlet weak var tableView: NSTableView!
// MARK: - Life cycle
override func viewWillAppear() {
super.viewWillAppear()
let model1 = Contact(id: 1, firstName: "Christopher", lastName: "Wilson", dateOfBirth: "06-02-2001")
let model2 = Contact(id: 2, firstName: "Jen", lastName: "Psaki", dateOfBirth: "08-25-1995")
let model3 = Contact(id: 3, firstName: "Pete", lastName: "Marovich", dateOfBirth: "12-12-2012")
let model4 = Contact(id: 4, firstName: "Deborah", lastName: "Mynatt", dateOfBirth: "11-08-1999")
let model5 = Contact(id: 5, firstName: "Christof", lastName: "Kreb", dateOfBirth: "01-01-2001")
let models = [model1, model2, model3, model4, model5]
dataSource = NSTableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, tableColumn, row, identifier in
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: self) as! TestTableCellView
let model = models[row]
cell.firstField.stringValue = model.firstName
cell.lastField.stringValue = model.lastName
cell.dobField.stringValue = model.dateOfBirth
return cell
})
tableView.dataSource = dataSource
guard let dataSource = self.dataSource else {
return
}
var snapshot = dataSource.snapshot()
snapshot.appendSections([0])
snapshot.appendItems(models, toSection: 0)
dataSource.apply(snapshot, animatingDifferences: true, completion: nil) // <--- crashing...
}
}
struct Contact: Hashable {
var id: Int
var firstName: String
var lastName: String
var dateOfBirth: String
}
Hmm... The application crashes with an error "Invalid parameter not satisfying: snapshot." A couple of days ago, I tested another example, which also crashed at the same line (dataSource.apply). I don't find many examples involving NSTableViewDiffableDataSource online. The only example I have found is this topic, which doesn't help. Anyway, what am I doing wrong? My Xcode version is 13.1. Thanks.
Create a snapshot like this and it should work:
guard let dataSource = self.dataSource else {
return
}
var snapshot = NSDiffableDataSourceSnapshot<Int, Contact>()
snapshot.appendSections([0])
snapshot.appendItems(models, toSection: 0)
dataSource.apply(snapshot, animatingDifferences: false)
Related
So i'm going to send data back and forth between 2 applications and i thought since it's A LOT of data in different models i can use a UIPasteboard. However i can't get it to work. Here's the code and issue.
struct TestModel {
var value1: Int = 0
var value2: String = "hi"
}
class ViewController: UIViewController {
#IBOutlet weak var redView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let model = TestModel(value1: 1, value2: "hello")
let testModel = Mirror.init(reflecting: model)
guard let paste = UIPasteboard.init(name: UIPasteboard.Name(rawValue: "test1"), create: true) else {return}
let items = testModel.children.map({
return [$0.label ?? "noLabel": $0.value]
})
print(items)
paste.addItems(items)
print(paste.items[0]["value1"])
}
}
print(items) gives me the following:
[["value1": 1], ["value2": "hello"]]
So everything is working so far.
print(paste.items[0]["value1"]) however gives me:
<OS_dispatch_data: data[0x2835d3cc0] = { leaf, size = 43, buf = 0x102f18000 }>
And i have no clue how to unwrap this, i've tried unwrapping it to Int, String, Data, NSData and everything, but nothing seem to work.. so what is going on?
I've spent a couple days trying to fix this but I can't.
Im getting this error Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) when I try to display data from Google Firestore into a table view.
Im using the same code in other view controllers (linking to another collection database) and it works great. I don't know what's happening and it is really doing my head in.
The error appears in this area of the code // Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(suppliersProducts.self) with dictionary \(document.data())")
Here is the full code:
import UIKit
import XLPagerTabStrip
import Firebase
import FirebaseFirestore
import Kingfisher
class supplierProductsCellOne: UITableViewCell {
#IBOutlet weak var supplierImage: UIImageView!
#IBOutlet weak var supplierName: UILabel!
#IBOutlet weak var supplierPrice: UILabel!
#IBOutlet weak var supplierDescription: UILabel!
}
class allProductsTableViewController: UITableViewController, IndicatorInfoProvider {
// Variables
var products: [suppliersProducts] = []
var filteredSupplier: [suppliersProducts] = []
var selectedIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Hide keyboard
hideKeyboardOrangutan()
// Data sources
tableView.dataSource = self
tableView.delegate = self
query = baseQuery()
print(Firestore.firestore().collection("supplierOneSingle").limit(to: 200))
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
observeQuery()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopObserving()
}
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
observeQuery()
}
}
}
private var listener: ListenerRegistration?
fileprivate func observeQuery() {
guard let query = query else { return }
stopObserving()
// Display data from Firestore, part one
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> suppliersProducts in
if let model = suppliersProducts(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(suppliersProducts.self) with dictionary \(document.data())")
}
}
self.products = models
self.tableView.reloadData()
}
}
fileprivate func stopObserving() {
listener?.remove()
}
// Firestore database
fileprivate func baseQuery() -> Query {
return Firestore.firestore().collection("supplierOneSingle").limit(to: 200)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
// Firestore data
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! supplierProductsCellOne
let product = products[indexPath.row]
let url = URL(string: product.productImageSquare)
cell.supplierName?.text = product.name
cell.supplierDescription?.text = product.description
cell.supplierPrice?.text = product.unitPrice
DispatchQueue.main.async{
cell.supplierImage?.kf.setImage(with: url)
}
return cell
}
// Slider menu indicator
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title: "ALL PRODUCTS")
}
}
And here is the debugger instructions.
Please audit all existing usages of Date when you enable the new behavior. In a future release, the behavior will be changed to the new behavior, so if you do not follow these steps, YOUR APP MAY BREAK.
Fatal error: Unable to initialize type suppliersProducts with dictionary ["Quantity": 1 Pack, "Product Image Square": https://firebasestorage.googleapis.com/v0/b/honcho-app.appspot.com/o/supplierProducts%2FsquarePhotos%2FProduct-square-5.png?alt=media&token=beff70f8-93e1-44c2-8695-9af27efaccf5, "Suggested Price": 25000, "Category": Personal Care, "Unit Price": 21000, "Brand": Laurier, "Supplier ID": 101, "Product Image Rectangle": https://firebasestorage.googleapis.com/v0/b/honcho-app.appspot.com/o/supplierProducts%2FrectanglePhotos%2FProduct-5.png?alt=media&token=520649d0-e76c-45c6-a4eb-c0bdd69eb0f1, "Description": Sofy, dry Double Air Layered surface absorbs blood quickly and the Powerful Center Core locks it inside to prevent leakage, "Size": 8 Pads, "Name": Active Day Pads, "Unit SKU": 8992727000048]: file /Users/eugenio/Documents/yProjects/Honcho/Xcode/merchant-bangkok-bank/merchant/allProductsTableViewController.swift, line 88
2018-11-23 09:15:31.622656+1300 Youtap Merchant[12116:323460] Fatal error: Unable to initialize type suppliersProducts with dictionary ["Quantity": 1 Pack, "Product Image Square": https://firebasestorage.googleapis.com/v0/b/honcho-app.appspot.com/o/supplierProducts%2FsquarePhotos%2FProduct-square-5.png?alt=media&token=beff70f8-93e1-44c2-8695-9af27efaccf5, "Suggested Price": 25000, "Category": Personal Care, "Unit Price": 21000, "Brand": Laurier, "Supplier ID": 101, "Product Image Rectangle": https://firebasestorage.googleapis.com/v0/b/honcho-app.appspot.com/o/supplierProducts%2FrectanglePhotos%2FProduct-5.png?alt=media&token=520649d0-e76c-45c6-a4eb-c0bdd69eb0f1, "Description": Sofy, dry Double Air Layered surface absorbs blood quickly and the Powerful Center Core locks it inside to prevent leakage, "Size": 8 Pads, "Name": Active Day Pads, "Unit SKU": 8992727000048]: file /Users/eugenio/Documents/yProjects/Honcho/Xcode/merchant-bangkok-bank/merchant/allProductsTableViewController.swift, line 88
(lldb)
Thanks heaps for the help
here's the code of suppliersProducts(dictionary)
import Foundation
import Firebase
protocol DocumentSerializableSuppliersProducts {
init?(dictionary: [String: Any])
}
struct suppliersProducts {
var brand: String
var category: String
var description: String
var name: String
var productImageRectangle: String
var productImageSquare: String
var quantity: String
var size: String
var suggestedPrice: Int
var supplierID: Int
var unitPrice: Int
var unitSKU: Int
var dictionary: [String: Any] {
return [
"Brand": brand,
"Category": category,
"Description": description,
"Name": name,
"Product Image Rectangle": productImageRectangle,
"Product Image Square": productImageSquare,
"Quantity": quantity,
"Size": size,
"Suggested Price": suggestedPrice,
"Supplier ID": supplierID,
"Unit Price": unitPrice,
"Unit SKU": unitSKU
]
}
}
extension suppliersProducts: DocumentSerializableSuppliersProducts{
init?(dictionary: [String : Any]) {
guard let name = dictionary["Name"] as? String,
let brand = dictionary["Brand"] as? String,
let category = dictionary["Category"] as? String,
let productImageRectangle = dictionary["Product Image Rectangle"] as? String,
let productImageSquare = dictionary["Product Image Square"] as? String,
let quantity = dictionary["Quantity"] as? String,
let size = dictionary["Size"] as? String,
let suggestedPrice = dictionary["Suggested Price"] as? Int,
let supplierID = dictionary["Supplier ID"] as? Int,
let unitPrice = dictionary["Unit Price"] as? Int,
let unitSKU = dictionary["Unit SKU"] as? Int
else {return nil}
let description = dictionary["Description"] as? String
let defaultDescription: String = description ?? "This item has not yet been properly described by our team of writing monkeys"
self.init(brand: brand, category: category, description: defaultDescription, name: name, productImageRectangle: productImageRectangle, productImageSquare: productImageSquare, quantity: quantity, size: size, suggestedPrice: suggestedPrice, supplierID: supplierID, unitPrice: unitPrice, unitSKU: unitSKU)
}
}
I'm trying to use a Struct to pass some variables that I get from a Facebook Graph Request such as email, name, gender, etc.
I've created the Struct ('fbDemographics') and the variables in 'ViewController' but I get an error when I try to call the struct and one of the variables in 'SecondViewController' (Type 'ViewController' has no member 'fbDemographics'). I've never used struct before so a bit baffled why I am getting this error. Thanks for any thoughts. The code for both view controllers is below:
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
struct fbDemographics {
static var relationship_status: String?
static var gender: String?
static var user_education_history: String?
static var user_location: String?
static var email: String?
static var name: String?
}
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
//let fbDetails = result as! NSDictionary
//print(fbDetails)
if let userDataDict = result as? NSDictionary {
fbDemographics.gender = userDataDict["gender"] as? String
fbDemographics.email = userDataDict["email"] as? String
fbDemographics.name = userDataDict["name"] as? String
fbDemographics.user_location = userDataDict["user_location"] as? String
fbDemographics.user_education_history = userDataDict["user_education_history"] as? String
fbDemographics.relationship_status = userDataDict["relationship_status"] as? String
let myEducation = fbDemographics.user_education_history
let myEmail = fbDemographics.email
let myGender = fbDemographics.gender
let myName = fbDemographics.name
let myStatus = fbDemographics.relationship_status
let myLocation = fbDemographics.user_location
self.performSegue(withIdentifier: "LoginToHome", sender: (Any).self)
}
}
SECOND VIEW CONTROLLER
class SecondViewController: UIViewController {
#IBAction func verticalSliderChanged(_ sender: UISlider) {
let currentValue = String(sender.value);
sliderLabel.text = "\(currentValue)"
func viewDidLoad() {
super.viewDidLoad()
***ViewController.fbDemographics.myEmail***
}
}
The problem is that you've defined your struct inside viewDidLoad. So the scope is limited to that method. Move it out of viewDidLoad, but still in ViewController, and you should be able to access it from the SecondViewController. Obviously, you'll have to fix that reference to myEmail, too, because it's called email. Also, in your SecondViewController you should pull viewDidLoad implementation out of the verticalSliderChanged method; the viewDidLoad should be a top-level instance method of SecondViewController, not defined inside another method.
There are deeper problems here, though. Rather than using struct with static variables, you really should make those simple instance variables, create an instance of your FbDemographics type (note, start struct types with uppercase letter), and then pass this instance in prepare(for:sender:).
For example, the right way to pass data would be to:
eliminate static variables;
give your struct a name that starts with uppercase letter;
create instance of your struct; and
pass this instance to the destination view controller in prepare(for:sender:).
E.g.
struct FbDemographics {
var relationshipStatus: String?
var gender: String?
var userEducationHistory: String?
var userLocation: String?
var email: String?
var name: String?
}
class ViewController: UIViewController {
var demographics: FbDemographics?
override func viewDidLoad() {
super.viewDidLoad()
performRequest() // I actually don't think you should be initiating this in `viewDidLoad` ... perhaps in `viewDidAppear`
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SecondViewController {
destination.demographics = demographics
}
}
func performRequest() {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start { connection, result, error in
guard let userDataDict = result as? NSDictionary, error == nil else {
print("\(error)")
return
}
self.demographics = FbDemographics(
relationshipStatus: userDataDict["relationship_status"] as? String,
gender: userDataDict["gender"] as? String,
userEducationHistory: userDataDict["user_education_history"] as? String,
userLocation: userDataDict["user_location"] as? String,
email: userDataDict["email"] as? String,
name: userDataDict["name"] as? String
)
self.performSegue(withIdentifier: "LoginToHome", sender: self)
}
}
}
And then SecondViewController could:
class SecondViewController: UIViewController {
var demographics: FbDemographics!
override func viewDidLoad() {
super.viewDidLoad()
let value = demographics.email // this should work fine here
}
}
I am looking to have a data model that is created by the user in the main View Controller and then passed into other view controllers via prepareForSegue.
However in my View Controller I am unable to use the model and I get errors for unwrapped optional values.
I have :
Class Collection: NSObject {
var id: String!
var image: String!
var apples: Int?
var oranges: Int?
var lemons: Int?
}
init(id: String, image: String, apples: Int, oranges: Int, lemons: Int) {
super.init()
self.id = id
self.photo = photo
self.apples = apples
self.oranges = oranges
self.lemons = lemons
}
View Controller:
var collection: Collection!
...
// if user selects a number ..
self.collection.oranges = usersSelection
self.collection.apples = usersSelection
etc
Can anyone tell me what I'm doing wrong please?
Thank you!
Define your model class like this:
class Collection: NSObject {
var id: String!
var image: String!
var apples: Int?
var oranges: Int?
var lemons: Int?
init(id: String, image: String, apples: Int?, oranges: Int?, lemons: Int?) {
super.init()
self.id = id
self.image = image
if let _ = apples{
self.apples = apples
}
if let _ = oranges{
self.oranges = oranges
}
if let _ = lemons{
self.lemons = lemons
}
}
}
Then implement in your ViewController like this:
class ViewController: UIViewController {
var collection: Collection!
override func viewDidLoad() {
collection = Collection(id: "user selection", image: "useselection", apples: nil, oranges: nil, lemons: nil)
// collection.image =
// colection.oranges =
// ........
}
}
//Patient class
import Foundation
struct Patients {
var family: NSArray
var given: NSArray
var id: String
var birthdate:String
var gender: String
}
struct Address {
var city: String
var country: String
var line: NSArray
}
class Patient {
var flag = 0
var address = Address(city: "", country: "", line: [""])
var patient_info = Patients(family: [""], given: [""], id: "", birthdate: "", gender: "")
var response : AnyObject?
init(response: AnyObject) {
self.response = response
if let entry = response.objectForKey("entry") {
//MARK: Address
if let resource = entry[0].objectForKey("resource") {
if let add = resource.objectForKey("address") {
address.city = add[0].objectForKey("city")! as! String
address.country = add[0].objectForKey("country")! as! String
address.line = add[0].objectForKey("line")! as! NSArray
//MARK: patient
patient_info.birthdate = resource.objectForKey("birthDate")! as! String
patient_info.gender = resource.objectForKey("gender")! as! String
if let name = resource.objectForKey("name") {
patient_info.family = name[0].objectForKey("family")! as! NSArray
patient_info.given = name[0].objectForKey("given")! as! NSArray
}
}
}
//MARK: id
if let link = entry[0].objectForKey("link") {
if let url = link[0].objectForKey("url") {
let id = url.componentsSeparatedByString("/")
patient_info.id = id[id.count-1]
}
}
}
print(patient_info)
}
}
//ViewController class
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var viewModel = ViewModel()
#IBOutlet weak var family_name: UITextField!
#IBOutlet weak var given_name: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
family_name.delegate = self
given_name.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
switch textField {
case family_name:
family_name.resignFirstResponder()
given_name.becomeFirstResponder()
case given_name:
given_name .resignFirstResponder()
default:
print("")
}
return true
}
#IBAction func search(sender: UIButton) {
let family_name1 = family_name.text!
let given_name1 = given_name.text!
viewModel .searchForPatient(family_name1, given_name: given_name1)
//When the name property from my patient class changed I can call the //below method. How to implement the observer?
performSegueWithIdentifier("showSegue", sender:sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?){
if segue.identifier == "showPatientSegue" {
if let displayViewController = segue.destinationViewController as? DisplayViewController {
displayViewController.viewModelDisplay.patient = viewModel.patient
}
}
}
}
// ViewModel where I make the request.
import Foundation
import Alamofire
import SystemConfiguration
class ViewModel {
var patient = Patient!()
func searchForPatient(family_name: String, given_name : String) {
let header = ["Accept" : "application/json"]
Alamofire.request(.GET, "https://open-ic.epic.com/FHIR/api/FHIR/DSTU2/Patient?family=\(family_name)&given=\(given_name)", headers: header).responseJSON { response in
self.patient = Patient(response: response.result.value!)
}
}
func checkInternetConnection() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
}
The problem is that the view loads fester than the request and I need to observe when a property has been changed in my Patient class, so the view can be loaded. If the view loads faster than the request I can't display the Patient information which I need.
You have lots options:
Store a delegate (weak!) object to the ViewController so that when your patient finishes, you can load the view. In the meantime, display something sensible in the view instead.
Send an NSNotification out, which the ViewController is a listener for.
KVO (Explanation of it here, just search for 'key-value observing'), which would require your Patient object to extend NSObject so that you can leverage objc's KVO.
Hope that helps!
You can add an observer on your variable this way :
var yourVariable:String!{
didSet{
refreshView()
}
}