Swift: Passing { $0.property } as a closure - swift

Playground Code is below.
Question I have is for the following line:
let recentItemsVC = ItemsViewController(items: recentItems, cellDescriptor: {$0.cellDescriptor })
I know ItemsViewController takes a closure for cellDescriptor, but can't wrap my head around what {$0.cellDescriptor} by itself means. There is no filter, map, compactMap, filter, etc. called here.
Two questions:
1. Playground shows (4 times). What does that mean?
2. What does passing { $0.cellDescriptor } by itself mean?
Code:
import UIKit
import PlaygroundSupport
struct Album {
var title: String
}
struct Artist {
var name: String
}
struct CellDescriptor {
let cellClass: UITableViewCell.Type
let reuseIdentifier: String
let configure: (UITableViewCell) -> ()
init<Cell: UITableViewCell>(reuseIdentifier: String, configure: #escaping (Cell) -> ()) {
self.cellClass = Cell.self
self.reuseIdentifier = reuseIdentifier
self.configure = { cell in
configure(cell as! Cell)
}
}
}
final class ItemsViewController<Item>: UITableViewController {
var items: [Item] = []
let cellDescriptor: (Item) -> CellDescriptor
var didSelect: (Item) -> () = { _ in }
var reuseIdentifiers: Set<String> = []
init(items: [Item], cellDescriptor: #escaping (Item) -> CellDescriptor) {
self.cellDescriptor = cellDescriptor
super.init(style: .plain)
self.items = items
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = items[indexPath.row]
didSelect(item)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let descriptor = cellDescriptor(item)
if !reuseIdentifiers.contains(descriptor.reuseIdentifier) {
tableView.register(descriptor.cellClass, forCellReuseIdentifier: descriptor.reuseIdentifier)
reuseIdentifiers.insert(descriptor.reuseIdentifier)
}
let cell = tableView.dequeueReusableCell(withIdentifier: descriptor.reuseIdentifier, for: indexPath)
descriptor.configure(cell)
return cell
}
}
final class ArtistCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class AlbumCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .value2, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let artists: [Artist] = [
Artist(name: "Prince"),
Artist(name: "Glen Hansard"),
Artist(name: "I Am Oak")
]
let albums: [Album] = [
Album(title: "Blue Lines"),
Album(title: "Oasem"),
Album(title: "Bon Iver")
]
enum RecentItem {
case artist(Artist)
case album(Album)
}
let recentItems: [RecentItem] = [
.artist(artists[0]),
.artist(artists[1]),
.album(albums[1])
]
extension Artist {
func configureCell(_ cell: ArtistCell) {
cell.textLabel?.text = name
}
}
extension Album {
func configureCell(_ cell: AlbumCell) {
cell.textLabel?.text = title
}
}
extension RecentItem {
var cellDescriptor: CellDescriptor {
switch self {
case .artist(let artist):
return CellDescriptor(reuseIdentifier: "artist", configure: artist.configureCell)
case .album(let album):
return CellDescriptor(reuseIdentifier: "album", configure: album.configureCell)
}
}
}
let recentItemsVC = ItemsViewController(items: recentItems, cellDescriptor: {$0.cellDescriptor })
let nc = UINavigationController(rootViewController: recentItemsVC)
nc.view.frame = CGRect(x: 0, y: 0, width: 200, height: 300)
PlaygroundPage.current.liveView = nc.view

AFAIK 4 times is the count of recentItems + 1
{$0.cellDescriptor} is a closure. Its signature is #escaping (Item) -> CellDescriptor. that means that it takes a generic type Item and returns a CellDescriptor. In this case, for each element of recentItems it will return that element's cellDescriptor. Whether it's an artist or an album, cellDescriptor is defined by this part of the code:
extension RecentItem {
var cellDescriptor: CellDescriptor {
switch self {
case .artist(let artist):
return CellDescriptor(reuseIdentifier: "artist", configure: artist.configureCell)
case .album(let album):
return CellDescriptor(reuseIdentifier: "album", configure: album.configureCell)
}
}
}
Which sets the reuseIdentifier and configures the cell by setting textLabel?.text to be either the artist's name or the title of the album.

it's that line of code:
init(items: [Item], cellDescriptor: #escaping (Item) -> CellDescriptor) {
You initialize the ItemsViewController with an array of Item and a callback method. The callback methods accepts an Item as parameter. And Item has a property named cellDescriptor

Related

How to set TableView content bottom position constraint to safe area in swift?

I created a generic UITableView that works like a dropdown that allows selection and is searchable. When I create a TableView footer (button), the Tableview content is overlapping the footer like the image below.
I tried using self.tableView.contentInsetAdjustmentBehavior = .always but nothing happens:
My expectation is that the table row constraint with the footer. How can I do it?
public class GenericSelectedTableViewController<T, Cell: UITableViewCell>: UITableViewController, UISearchBarDelegate {
private lazy var searchBar: UISearchBar = {
let view = UISearchBar()
view.placeholder = placeholder
view.searchBarStyle = .default
view.delegate = self
view.sizeToFit()
return view
}()
private var initial: [T]
private var items: [T]
private var configure: (Cell, T) -> Void
private var selectHandler: (T) -> Void
private var searchHandler: (([T], String) -> [T])?
private var isSearch:Bool = true
private var titleNav:String?
private var placeholder:String?
public init(isSearch:Bool = true, titleNav: String = "Harap Pilih",placeholder:String = "Cari" ,items: [T], configure: #escaping (Cell, T) -> Void, selectHandler: #escaping (T) -> Void, searchHandler: (([T], String) -> [T])? = nil) {
self.initial = items
self.items = items
self.configure = configure
self.selectHandler = selectHandler
self.searchHandler = searchHandler
self.isSearch = isSearch
self.titleNav = titleNav
self.placeholder = placeholder
super.init(style: .plain)
let bundle = Bundle(for: Cell.self)
if let _ = bundle.path(forResource: String(describing: Cell.self), ofType: "nib") {
let nib = UINib(nibName: String(describing: Cell.self), bundle: bundle)
self.tableView.register(nib, forCellReuseIdentifier: "Cell")
} else {
self.tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
}
self.tableView.register(UINib(nibName: "GenericButton", bundle: Bundle(for: GenericSelectedTableViewController.self)), forHeaderFooterViewReuseIdentifier: "GenericButton")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.configure(with: .solid(color: .white))
navigationItem.title = titleNav
if isSearch{
tableView.tableHeaderView = searchBar
}
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isModal {
navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "arrowBack", in: Bundle(identifier: "com.dkf.Commons"), compatibleWith: nil), style: .plain, target: self, action: #selector(didSelectCancel(_:)))
navigationController?.navigationBar.configure(with: .solid(color: .white))
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.tintColor = UIColor.black
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
}
#objc
private func didSelectCancel(_ sender: UIBarButtonItem) {
dismiss(animated: true)
}
public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
let item = items[indexPath.row]
cell.tag = indexPath.row
configure(cell, item)
return cell
}
public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = items[indexPath.row]
// if isModal {
// dismiss(animated: true) { [weak self] in
// self?.selectHandler(item)
// }
// } else {
// self.selectHandler(item)
// navigationController?.popViewController(animated: true)
// }
}
public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
items = initial
} else {
items = searchHandler?(initial, searchText) ?? []
}
tableView.reloadData()
}
public override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "GenericButton") as? GenericButton else {
return nil
}
return view
}
public override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 40
}
}

fatal error: init(coder:) has not been implemented error

Xcode is sending me the error "fatal error: init(coder:) has not been implemented error" despite being implemented. I don't know where the problem is.
final class newTableViewController: UITableViewController {
#IBOutlet var inputFormTableView: UITableView!
let form: Form
let note = Note(name: "")
init(form: Form) {
self.form = form
super.init(style: .grouped)
}
convenience init(note: Note) {
let form = Form(sections: [
FormSection(items: [
TextInputFormItem(text: note.name,
placeholder: "Add title",
didChange: { _ in print("hello")})
])
])
self.init(form: form)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
inputFormTableView.rowHeight = 44
inputFormTableView.register(TextInputTableViewCell.self, forCellReuseIdentifier: ReuseIdentifiers.textInput.rawValue)
let new = newTableViewController(note: note)
print(new.note.name)
}
private enum ReuseIdentifiers: String {
case textInput
}
// MARK: - Table view data source
private func model(at indexPath: IndexPath) -> FormItem {
return form.sections[indexPath.section].items[indexPath.item]
}
override func numberOfSections(in tableView: UITableView) -> Int {
return form.sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return form.sections[section].items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = model(at: indexPath)
if let textRow = object as? TextInputFormItem {
let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifiers.textInput.rawValue, for: indexPath) as! TextInputTableViewCell
cell.configure(for: textRow)
return cell
}
else {
fatalError("Unknown model \(object).")
}
}
}
I am trying to make UITableView act like an input form. For doing this, I am following this tutoriel : https://augmentedcode.io/2018/11/04/text-input-in-uitableview/. Everything works in the sample project but not mine.
You haven't implemented it, you are just throwing a fatalError, when running from stroy-board this init is getting executed, replace the following code:
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
With this:
required init?(coder: NSCoder) {
form = Form()
super.init(coder: coder)
}

Index out of range when selecting reusable tableview cell

I have the following class to keep track of all of the cells selected in the tableview, the issue is I keep receiving the error Index out of Range on the line:
self!.myModels[indexPath.row].isSelected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
What is causing this? The issue occurs as soon as the tableView loads.
Rest of the code:
class CheckableTableViewCell: UITableViewCell {
var handler: ((Bool)->())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
handler?(selected)
}
}
class TableViewController: UITableViewController {
private var myModels: [MyModel] = [MyModel(isSelected: false),MyModel(isSelected: true),MyModel(isSelected: true),MyModel(isSelected: false) ]
var sections = [Section]()
var structure = [Portfolios]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self] selected in
self?.myModels[indexPath.row].isSelected = selected
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let titleStr = "\(item.code)
cell.textLabel!.text = titleStr
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndexPath = indexPath.row
myModels[selectedIndexPath].isSelected = true
self.tableView.reloadRows(at: [indexPath], with: .none)
}
Sections defines the following tableView struct:
struct Section {
let name : String
var items : [Portfolios]
}
struct Portfolios: Decodable {
let code: String
enum CodingKeys : String, CodingKey {
case code
}
}

Swift 3 Fetch User Data from Firebase

I want to fetch all user's first name on my Firebase database to table view cells. When I excute my code, it does not work. Firebase has user tuple. Inside of this tuple, I am storing all user's id. Inside of these id's, I am storing personalInfo tuple. Firstname is inside of this personalInfo. You can see my Firebase snapshot on below picture. Where is my problem on code ? Can anyone help me ? http://prntscr.com/huvdr9
//chatInfo.swift
class ChatInfo: UITableViewController {
let cellId = "cellId"
var users = [User] ()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Geri", style: .plain, target:self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
fetchUser()
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
func fetchUser() {
Database.database().reference().child("user").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.userId = dictionary["firstname"] as! String
print(user.firstname)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
} , withCancel: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.firstname
cell.imageView?.image = UIImage(named: "mail")
return cell
}
class UserCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("not implemented")
}
}
}
//user.swift
class User: NSObject {
var userId : String?
var latitude : Double?
var longitude : Double?
var firstname : String?
var email : String?
}
You need to add a child for the user on line:
Database.database().reference().child("user").observe(.childAdded, with: {(snapshot) in
change it to this:
Database.database().reference().child("user").child("personalInfo").observe(.childAdded, with: {(snapshot) in

How do I filter data and return only the relevant users?

I have a tableView in my application that returns all of the users inside of my users node in my Firebase Database. I have a picture of my database tree below, along with the relevant code. How do I get the tableView to return only the users with the numberId set to 1. As I only have 2 users in my database, I want it to return that one user and not the user with the numberId set to 2. At the moment, it returns all of the users. I would appreciate any help. Thank you.
class User: NSObject {
var name: String?
var email: String?
var numberId: String?
var password: String?
var profileImageUrl: String?
}
class UsersController: UITableViewController {
let cellId = "cellId"
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: "cellId")
tableView.tableFooterView = UIView()
fetchUser()
}
func fetchUser() {
FIRDatabase.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let user = users[indexPath.row]
cell.textLabel?.text = user.name
cell .detailTextLabel?.text = user.email
if let profileImageUrl = user.profileImageUrl {
cell.profileImageView.loadImageUsingCachWithUrlString(urlString: profileImageUrl)
}
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 56
}
}
class UserCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
textLabel?.frame = CGRect(x: 58, y: (textLabel?.frame.origin.y)! - 2, width: (textLabel?.frame.width)!, height: (textLabel?.frame.height)!)
detailTextLabel?.frame = CGRect(x: 58, y: (detailTextLabel?.frame.origin.y)! + 2, width: (detailTextLabel?.frame.width)!, height: (detailTextLabel?.frame.height)!)
}
let profileImageView: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.layer.masksToBounds = true
image.layer.cornerRadius = 20
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
setupProfileImageView()
}
func setupProfileImageView() {
addSubview(profileImageView)
profileImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
profileImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You need to use queryOrderedByChild and queryEqualToValue.
func fetchUser() {
FIRDatabase.database().reference().child("users").queryOrderedByChild("numberId").queryEqualToValue(1).observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
You can have idea from here, I don't know about swift how to write code in but.. you can have idea and hope you can write code like wise.