My goal is to display data coming from a web service called News API into a collection view controller. Below is the data model and network manager.
struct News: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]?
var article: Article?
enum CodingKeys: String, CodingKey {
case status = "status"
case totalResults = "totalResults"
case articles = "articles"
}
}
// MARK: - Article
struct Article: Codable {
var source: Source?
var author: String?
var title: String?
var articleDescription: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
enum CodingKeys: String, CodingKey {
case source = "source"
case author = "author"
case title = "title"
case articleDescription = "description"
case url = "url"
case urlToImage = "urlToImage"
case publishedAt = "publishedAt"
case content = "content"
}
}
// MARK: - Source
struct Source: Codable {
var id: ID?
var name: Name?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
enum ID: String, Codable {
case engadget = "engadget"
case techcrunch = "techcrunch"
case theVerge = "the-verge"
}
enum Name: String, Codable {
case engadget = "Engadget"
case lifehackerCOM = "Lifehacker.com"
case techCrunch = "TechCrunch"
case theVerge = "The Verge"
}
class NetworkManger{
static let shared = NetworkManger()
private let baseURL: String
private var apiKeyPathCompononent :String
private init(){
self.baseURL = "https://newsapi.org/v2/everything?q=NFT&sortBy=popularity&"
self.apiKeyPathCompononent = "apiKey=d32071cd286c4f6b9c689527fc195b03"
}
private var jsonDecoder:JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
func getArticles() {
AF.request(self.baseURL + self.apiKeyPathCompononent, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).response { (responseData) in
guard let data = responseData.data else {return}
do {
let news = try self.jsonDecoder.decode(News.self, from: data)
let nc = NotificationCenter.default
nc.post(name: Notification.Name("didFinishParsing"), object: nil)
} catch {
print(error)
}
}
}
}
It can display data from the web service to the console. The problem is that it can not show data to the collection view in NewsVC. I've done all necessary steps such as implementing the collection view data source, using an observer to alert the NewsVC that the JSON is parsed, setting the collection view layout, and registering the cell, but nothing seems to work. The code below is the NewVc and News grid; News Vc is meant to show the contents of the.
class NewsVC: UIViewController {
var news = [Article]()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
configureCollection()
NetworkManger.shared.getArticles()
}
func configureCollection(){
collectionView.delegate = self
collectionView.dataSource = self
let collectionviewFlowLayout = UICollectionViewFlowLayout()
collectionviewFlowLayout.scrollDirection = .vertical
collectionviewFlowLayout.itemSize = CGSize(width: 188.0, height: 264.0)
collectionviewFlowLayout.minimumInteritemSpacing = 10.0
collectionView.collectionViewLayout = collectionviewFlowLayout
NotificationCenter.default.addObserver(self, selector: #selector(refreshcollectionView), name: Notification.Name("didFinishParsing"), object: nil)
}
#objc func refreshcollectionView(_ notification:Notification) {
guard let news = notification.object as? Article else { return}
print("News == \(news)")
self.news = [news]
print("Coount == \(self.news.count)")
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
extension NewsVC:UICollectionViewDataSource , UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
news.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! NewsGridCell
let stories = news[indexPath.item]
cell.setCells(stories)
cell.backgroundColor = UIColor.red
return cell
}
class NewsGridCell: UICollectionViewCell {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsDescription: UILabel!
#IBOutlet weak var author: UILabel!
func setCells(_ news:Article){
upDateUI(newDescription:news.articleDescription, author: news.author)
}
private func upDateUI(newDescription:String? , author:String?){
self.newsDescription.text = newDescription
self.author.text = author
}
}
Try instead of
nc.post(name: Notification.Name("didFinishParsing"), object: nil)
send
nc.post(name: Notification.Name("didFinishParsing"), object: news)
Then instead of
guard let news = notification.object as? Article else { return}
write
guard let news = notification.object as? News else { return}
print("News == \(news)")
self.news = news.articles
After reading the snippets, the code looks like it should works but it doesn't. In that case I would add debugPrint(#function) to check if following methods are executed:
NewsVC -> collectionView(_:numberOfItemsInSection:)
NewsVC -> collectionView(_:cellForItemAt:)
NewsGridCell -> setCells(_:)
If those debugPrints print to the console then I would go to the "View Hierarchy inspector" to examine presence and a view's sizes.
Related
I have ViewModel for getting data from API. I want to pass this data to my UICollectionViewCell and show it on my ViewController but I don't know how.
I'm trying to delete extra information and leave useful information in code below:
My ViewModel:
class DayWeatherViewModel {
let url = "https://api.openweathermap.org/data/2.5/weather?q=London&appid=APIKEY"
func viewDidLoad() {
getData(from: url)
}
func getData(from url: String) {
guard let url = URL(string: url) else {
print("Failed to parse URL")
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: CitiesWeather?
do {
result = try JSONDecoder().decode(CitiesWeather.self, from: data)
self.weatherDidChange?(result!)
}
catch {
print("failed to convert \(error)")
}
guard let json = result else {
return
}
print(json.coord?.latitude)
print(json.coord?.longitude)
print(json.weather)
print(json.wind?.speed)
}
task.resume()
}
var weatherDidChange: ((CitiesWeather) -> Void)?
}
My UICollectionViewCell:
class DayWeatherCell: UICollectionViewCell, UIScrollViewDelegate {
struct Model {
let mainTemperatureLabel: Double
}
var mainTemperatureLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Rubik-Medium", size: 36)
label.text = "10"
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func configure(with model: Model) {
mainTemperatureLabel.text = String(model.mainTemperatureLabel)
}
My ViewController:
class MainScrenenViewController: UIViewController {
let viewModel: DayWeatherViewModel
private var main: Double? {
didSet {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(DayWeatherCell.self, forCellWithReuseIdentifier: "sliderCell")
collectionView.layer.cornerRadius = 5
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor(red: 0.125, green: 0.306, blue: 0.78, alpha: 1)
return collectionView
}()
init(viewModel: DayWeatherViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
setupConstraints()
viewModel.weatherDidChange = { result in
self.main = result.main?.temp
}
viewModel.viewDidLoad()
}
extension MainScrenenViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sliderCell", for: indexPath) as! DayWeatherCell
if let myd: String? = String(main ?? 1.1) {
cell.mainTemperatureLabel.text = myd
}
return cell
}
}
extension MainScrenenViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
My Struct for parse JSON:
struct CitiesWeather: Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather: [Weather]?
let sys: Sys?
let main: Main?
let wind: Wind?
let clouds: Clouds?
let dt: Date?
var timezone: Int?
}
struct Coordinate: Decodable {
var longitude: Double?
var latitude: Double?
}
struct Weather: Decodable {
let id: Int?
let main: MainEnum?
let description: String?
let icon: String?
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct Clouds: Decodable {
let all: Int?
}
enum MainEnum: String, Decodable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
I'm new to coding and could really use your help!
I am trying to show a 'bestseller' image on a product based on a boolean.
I am using Firestore for the database.
I have managed to get the value of the 'bestseller' field on all the documents, but I don't know what to do next.
This is my code so far. This shows the bestsellerImg on all of the products - instead of only the ones where the value = "True"
Here are two pictures to show what i mean :)
the swift file/class "ProductsVC" is controlling the ViewController with the collectionView in it.
Code from "ProductsVC"
import UIKit
import Firebase
class ProductsVC: UIViewController, ProductCellDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var categoryName: UILabel!
var products = [Product]()
var category: Category!
var db : Firestore!
var listener : ListenerRegistration!
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: Identifiers.ProductCell, bundle: nil), forCellWithReuseIdentifier: Identifiers.ProductCell)
setQuery()
categoryName.text = category.name
}
func setQuery() {
var ref: Query!
ref = db.products(category: category.id)
listener = ref.addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
}
snap?.documentChanges.forEach({ (change) in
let data = change.document.data()
let product = Product.init(data: data)
switch change.type {
case .added:
self.onDocumentAdded(change: change, product: product)
case .modified:
self.onDocumentModified(change: change, product: product)
case .removed:
self.onDoucmentRemoved(change: change)
}
})
})
}
func productAddToCart(product: Product) {
if UserService.isGuest {
self.simpleAlert(title: "Hej!", msg: "Man kan kun tilføje ting til sin kurv hvis man er oprettet som Fender-bruger. ")
return
}
PaymentCart.addItemToCart(item: product)
self.addedtocart(title: "Tilføjet til kurv!", msg: "")
}
}
extension ProductsVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func onDocumentAdded(change: DocumentChange, product: Product) {
let newIndex = Int(change.newIndex)
products.insert(product, at: newIndex)
collectionView.insertItems(at: [IndexPath(item: newIndex, section: 0)])
}
func onDocumentModified(change: DocumentChange, product: Product) {
if change.oldIndex == change.newIndex {
let index = Int(change.newIndex)
products[index] = product
collectionView.reloadItems(at: [IndexPath(item: index, section: 0)])
} else {
let oldIndex = Int(change.oldIndex)
let newIndex = Int(change.newIndex)
products.remove(at: oldIndex)
products.insert(product, at: newIndex)
collectionView.moveItem(at: IndexPath(item: oldIndex, section: 0), to: IndexPath(item: newIndex, section: 0))
}
}
func onDoucmentRemoved(change: DocumentChange) {
let oldIndex = Int(change.oldIndex)
products.remove(at: oldIndex)
collectionView.deleteItems(at: [IndexPath(item: oldIndex, section: 0)])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifiers.ProductCell, for: indexPath) as? ProductCell {
cell.configureCell(product: products[indexPath.item], delegate: self)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = DetailProductVC()
let selectedProduct = products[indexPath.item]
vc.product = selectedProduct
vc.modalTransitionStyle = .crossDissolve
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width
let cellWidth = (width - 30) / 3
let cellHeight = cellWidth * 2.1
return CGSize(width: cellWidth, height: cellHeight)
}
}
My struct
import Foundation
import FirebaseFirestore
struct Product {
var name: String
var id: String
var category: String
var price: Double
var productDescription: String
var imageUrl: String
var timeStamp: Timestamp
var inStore: Int
var bestseller: Bool
var quantity: Int
init(
name: String,
id: String,
category: String,
price: Double,
productDescription: String,
imageUrl: String,
timeStamp: Timestamp = Timestamp(),
inStore: Int,
bestseller: Bool,
quantity: Int) {
self.name = name
self.id = id
self.category = category
self.price = price
self.productDescription = productDescription
self.imageUrl = imageUrl
self.timeStamp = timeStamp
self.inStore = inStore
self.bestseller = bestseller
self.quantity = quantity
}
init(data: [String: Any]) {
name = data["name"] as? String ?? ""
id = data["id"] as? String ?? ""
category = data["category"] as? String ?? ""
price = data["price"] as? Double ?? 0.0
productDescription = data["productDescription"] as? String ?? ""
imageUrl = data["imageUrl"] as? String ?? ""
timeStamp = data["timeStamp"] as? Timestamp ?? Timestamp()
inStore = data["inStore"] as? Int ?? 0
bestseller = data["bestseller"] as? Bool ?? true
quantity = data["quantity"] as? Int ?? 0
}
static func modelToData(product: Product) -> [String: Any] {
let data : [String: Any] = [
"name" : product.name,
"id" : product.id,
"category" : product.category,
"price" : product.price,
"productDescription" : product.productDescription,
"imageUrl" : product.imageUrl,
"timeStamp" : product.timeStamp,
"inStore" : product.inStore,
"bestseller" : product.bestseller,
"quantity" : product.quantity
]
return data
}
}
extension Product : Equatable {
static func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.id == rhs.id
}
}
Code from "ProductCell"
import UIKit
import Kingfisher
import Firebase
protocol ProductCellDelegate : class {
func productAddToCart(product: Product)
}
class ProductCell: UICollectionViewCell{
#IBOutlet weak var imgView: UIImageView!
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var priceLbl: UILabel!
#IBOutlet weak var bestsellerImg: UIImageView!
var db: Firestore?
weak var delegate : ProductCellDelegate?
private var product: Product!
override func awakeFromNib() {
super.awakeFromNib()
imgView.layer.cornerRadius = 5
getInStore()
}
func getInStore() {
Firestore.firestore().collection("products").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents{
var isBestseller = document.get("bestseller")
}
}
}
}
func configureCell(product: Product, delegate: ProductCellDelegate) {
self.product = product
self.delegate = delegate
titleLbl.text = product.name
if let url = URL(string: product.imageUrl) {
let placeholder = UIImage(named: "Fender")
imgView.kf.indicatorType = .activity
let options : KingfisherOptionsInfo =
[KingfisherOptionsInfoItem.transition(.fade(0.1))]
imgView.kf.setImage(with: url, placeholder: placeholder, options: options)
}
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "DKK"
if let price = formatter.string(from: product.price as NSNumber) {
priceLbl.text = price
}
}
#IBAction func addToCart(_ sender: Any) {
delegate?.productAddToCart(product: product)
}
}
Looking at the code, there may be a pretty simple solution that would simplify what you're trying to do.
Let me walk through it and then make a suggestion:
The tableView datasource is populated in the setQuery function with
func setQuery() {
...
snap?.documentChanges.forEach({ (change) in
let data = change.document.data()
let product = Product.init(data: data)
Each product knows if it's a best seller because it has a best seller property which is either true or false. So when the products are loaded from firebase, that property is set with the Product.init.
As your tableView delegate is creating each cell
cell.configureCell(product: products[indexPath.item]
why don't you just have a piece of code in ProductCell that says if the product is a bestSeller then use the bestSeller image, else use the regular image?
func configureCell(product: Product, delegate: ProductCellDelegate) {
self.product = product
self.delegate = delegate
if self.product.bestSeller == true {
//set the image the bestseller image
} else {
//set the image to the regular image
}
I'm trying to pass the value at the indexpath to the next view controller but I'm unsure on how to do that.
var imageStore = [Data]()
var imageStoreReference = [(resultResponse, Data)]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let DestVC = self.navigationController?.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
let filtered = imageStoreReference.filter({imageStore.contains($0.1)}).first
DestVC.createdAt = filtered?.0.createdAt
DestVC.Imagedescription = filtered?.0.description
DestVC.instagram = filtered?.0.user.instagram
DestVC.twitter = filtered?.0.user.twitter
DestVC.portfolio = filtered?.0.user.portfolio
DestVC.fullImage = filtered?.0.urls.regular
DestVC.userProfileImage = filtered?.0.user.profileImageUrl.regular
}
Here is resultResponse, that is referenced in the tuple of imageStoreReference.
struct resultResponse: Codable {
let createdAt: String
let description: String?
let urls: urlResponse
let user: userResponse
}
struct urlResponse: Codable {
let regular: String
let small: String
}
struct userResponse: Codable {
let instagram: String?
let twitter: String?
let name:String?
let portfolio: String?
let profileImageUrl: imageSize
enum CodingKeys: String, CodingKey {
case instagram = "instagram_username"
case twitter = "twitter_username"
case profileImageUrl = "profile_image"
case name
case portfolio = "portfolio_url"
}
}
struct imageSize: Codable {
let regular: String?
}
You should create a variable which has type as "resultResponse" in DestVC.
Example:
class DestVC: UIViewController {
var filered: resultResponse?
}
In CollectionView you need only pass filtererd variable. It make your code clean.
And you should use "if let ... " to ensure your app cant crash when data nil
Example:
var imageStore = [Data]()
var imageStoreReference = [(String, Data)]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let DestVC = self.navigationController?.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
if let index = imageStoreReference.firstIndex(where: { (image) -> Bool in
imageStore.contains(image.1)
})
let filtered = imageStoreReference[index]
DestVC.filtered = filtered
}
I'm trying to parse to following JSON into a tableView : https://www.pathofexile.com/api/trade/data/items
I succeeded in parsing the first array, but I'm unable to parse the key "entries"...
Here's my code, with the data structure I defined :
import UIKit
struct ItemCategories: Codable {
var result: [ItemCategory]
}
struct ItemCategory: Codable {
var label: String
var entries: [Item]
}
struct Item: Codable {
// empty struct
}
class ViewController: UITableViewController {
let urlString = "https://www.pathofexile.com/api/trade/data/items"
var categories = [ItemCategory]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Path of Data"
navigationController?.navigationBar.prefersLargeTitles = true
parse()
}
func parse() {
guard let url = URL(string: urlString) else { return }
guard let data = try? Data(contentsOf: url) else { return }
let decoder = JSONDecoder()
guard let jsonItemCategories = try? decoder.decode(ItemCategories.self, from: data) else { return }
categories = jsonItemCategories.result
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
var categoryName = categories[indexPath.row].label
if categoryName == "" { categoryName = "Unknown" }
cell.textLabel?.text = categoryName
cell.textLabel?.textColor = .systemOrange
let numberOfItemsInCategory = String(categories[indexPath.row].entries.count)
cell.detailTextLabel?.text = numberOfItemsInCategory + " items"
return cell
}
}
The struct Item is empty, because when I try to add variable corresponding to the keys in the JSON, then the whole parsing fail (the tableView displays nothing).
When the struct Item is empty, then the parsing succeed and the tableView is able to display the different categories. It even display the number of items for each "entries" thanks to :
let numberOfItemsInCategory = String(categories[indexPath.row].entries.count)
cell.detailTextLabel?.text = numberOfItemsInCategory + " items"
Can someone explain why ? Ideally I would like to display the content of "entries" when the rows are tapped, but I can't figure out how for the moment.
Thanks for you help :)
screenshot
#Laurent Delorme Your Struct Item should be like below, try with this,
struct Item: Codable {
let name: String?
let type: String?
let text: String?
let flags: FlagsRepresentation?
enum CodingKeys: String, CodingKey {
case name
case type
case text
case flags
}
}
struct FlagsRepresentation: Codable {
let unique: Bool?
enum CodingKeys: String, CodingKey {
case unique
}
}
I have a Swift struct Reflection like this:
struct Reflection {
let title: String
let body: String
let author: String
let favorite: Bool
let creationDate: Date
let id: UUID
}
extension Reflection {
var plistRepresentation: [String: AnyObject] {
return [
"title": title as AnyObject,
"body": body as AnyObject,
"author": author as AnyObject,
"favorite": favorite as AnyObject,
"creationDate": creationDate as AnyObject,
"id": id as AnyObject
]
}
init(plist: [String: AnyObject]) {
title = plist["title"] as! String
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = plist["id"] as! UUID
}
}
class StorageController {
fileprivate let documentsDirectoryURL = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)
.first!
fileprivate var notesFileURL: URL {
return documentsDirectoryURL
.appendingPathComponent("Notes")
.appendingPathExtension("plist")
}
func save(_ notes: [Reflection]) {
let notesPlist = notes.map { $0.plistRepresentation } as NSArray
notesPlist.write(to: notesFileURL, atomically: true)
}
func fetchNotes() -> [Reflection] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: AnyObject]] else {
return []
}
return notePlists.map(Reflection.init(plist:))
}
}
class StateController {
fileprivate let storageController: StorageController
fileprivate(set) var notes: [Reflection]
init(storageController: StorageController) {
self.storageController = storageController
self.notes = storageController.fetchNotes()
}
func add(_ note: Reflection) {
notes.append(note)
storageController.save(notes)
}
func update(_ note: Reflection) {
for (index, storedNote) in notes.enumerated() {
guard storedNote.id == note.id else {
continue
}
notes[index] = note
storageController.save(notes)
break
}
}
}
Instantiating a Reflection like this in viewWillAppear crashes my app:
import UIKit
class NotesViewController: UIViewController {
var stateController: StateController!
fileprivate var dataSource: FeedDataSource!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let reflection = Reflection(title: "Hello", body: "world", author: "Alex", favorite: true, creationDate: Date(), id: UUID())
//stateController.add(reflection)
dataSource = FeedDataSource(notes: stateController.notes)
tableView.dataSource = dataSource
tableView.reloadData()
}
class FeedDataSource: NSObject {
var notes: [Reflection]!
init(notes: [Reflection]) {
self.notes = notes
}
}
extension FeedDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reflectionCell", for: indexPath) as! ReflectionCell
let index = indexPath.row
let note = notes[index]
cell.model = ReflectionCell.Model(data: note)
return cell
}
}
The cell class:
class ReflectionCell: UITableViewCell {
#IBOutlet weak fileprivate var titleLabel: UILabel!
#IBOutlet weak fileprivate var bodyLabel: UILabel!
#IBOutlet weak fileprivate var authorLabel: UILabel!
#IBOutlet weak fileprivate var bookmarkButton: UIButton!
fileprivate var id: UUID!
var model: Model? {
didSet {
guard let model = model else {
return
}
titleLabel.text = model.title
bodyLabel.text = model.body
authorLabel.text = model.author
bookmarkButton.isSelected = model.favorite
id = model.id
}
}
override func awakeFromNib() {
super.awakeFromNib()
bookmarkButton.setImage(#imageLiteral(resourceName: "Bookmark-Highlighted"), for: .selected)
}
}
extension ReflectionCell {
struct Model {
let title: String
let body: String
let author: String
let favorite: Bool
let id: UUID
init(data: Reflection) {
title = data.title
body = data.body
author = data.author
favorite = data.favorite
id = data.id
}
}
}
I get no console output, just a main thread SIGABRT error. What could be going on?
Like an idiot I was cleaning up my code and commented out the line registering the nib for the reuse identifier. However, I do think it would help if Xcode could print out a useful error message for such a mistake.