Tuesday, February 7, 2023
Learning Code
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#
No Result
View All Result
Learning Code
No Result
View All Result
Home Swift

Working with diffable data sources and table views using UIKit

learningcode_x1mckf by learningcode_x1mckf
September 7, 2022
in Swift
0
Working with diffable data sources and table views using UIKit
74
SHARES
1.2k
VIEWS
Share on FacebookShare on Twitter


You might also like

The abstract Vapor service factory design pattern

SwiftNIO tutorial – The echo server

Introducing – Vapor cheatsheet – The.Swift.Dev.

Challenge setup

We’ll use a daily storyboard-based Xcode mission, since we’re working with UIKit.


We’re additionally going to want a desk view, for this goal we might go together with a traditional setup, however since we’re utilizing trendy UIKit practices we will do issues only a bit totally different this time.


It is fairly unlucky that we nonetheless have to offer our personal type-safe reusable extensions for UITableView and UICollectionView lessons. Anyway, here is a fast snippet that we’ll use. ⬇️


import UIKit

extension UITableViewCell 
    
    static var reuseIdentifier: String 
        String(describing: self)
    

    var reuseIdentifier: String 
        sort(of: self).reuseIdentifier
    


extension UITableView 
        
    func register<T: UITableViewCell>(_ sort: T.Kind) 
        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    

    func reuse<T: UITableViewCell>(_ sort: T.Kind, _ indexPath: IndexPath) -> T 
        dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T
    


I’ve additionally created a subclass for UITableView, so I can configure all the pieces contained in the initialize operate that we will want on this tutorial.


import UIKit

open class TableView: UITableView 

    public init(type: UITableView.Type = .plain) 
        tremendous.init(body: .zero, type: type)
        
        initialize()
    

    @accessible(*, unavailable)
    required public init?(coder: NSCoder) 
        fatalError("init(coder:) has not been carried out")
    
    
    open func initialize() 
        translatesAutoresizingMaskIntoConstraints = false
        allowsMultipleSelection = true
    
    
    func layoutConstraints(in view: UIView) -> [NSLayoutConstraint] 
        [
            topAnchor.constraint(equalTo: view.topAnchor),
            bottomAnchor.constraint(equalTo: view.bottomAnchor),
            leadingAnchor.constraint(equalTo: view.leadingAnchor),
            trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ]
    


We’re going to construct a settings display screen with a single choice and a a number of choice space, so it is good to have some extensions too that’ll assist us to handle the chosen desk view cells. 💡


import UIKit

public extension UITableView 
    
    func choose(_ indexPaths: [IndexPath],
                animated: Bool = true,
                scrollPosition: UITableView.ScrollPosition = .none) 
        for indexPath in indexPaths 
            selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition)
        
    
    

    func deselect(_ indexPaths: [IndexPath], animated: Bool = true) 
        for indexPath in indexPaths 
            deselectRow(at: indexPath, animated: animated)
        
    
    
    func deselectAll(animated: Bool = true) 
        deselect(indexPathsForSelectedRows ?? [], animated: animated)
    

    func deselectAllInSection(besides indexPath: IndexPath) 
        let indexPathsToDeselect = (indexPathsForSelectedRows ?? []).filter 
            $0.part == indexPath.part && $0.row != indexPath.row
        
        deselect(indexPathsToDeselect)
    


Now we will concentrate on making a customized cell, we’re going to use the brand new cell configuration API, however first we’d like a mannequin for our customized cell class.


import Basis

protocol CustomCellModel 
    var textual content: String  get 
    var secondaryText: String?  get 


extension CustomCellModel 
    var secondaryText: String?  nil 


Now we will use this cell mannequin and configure the CustomCell utilizing the mannequin properties. This cell can have two states, if the cell is chosen we will show a stuffed test mark icon, in any other case simply an empty circle. We additionally replace the labels utilizing the summary mannequin values. ✅


import UIKit

class CustomCell: UITableViewCell 

    var mannequin: CustomCellModel?

    override func updateConfiguration(utilizing state: UICellConfigurationState) 
        tremendous.updateConfiguration(utilizing: state)
        
        var contentConfig = defaultContentConfiguration().up to date(for: state)
        contentConfig.textual content = mannequin?.textual content
        contentConfig.secondaryText = mannequin?.secondaryText
        
        contentConfig.imageProperties.tintColor = .systemBlue
        contentConfig.picture = UIImage(systemName: "circle")

        if state.isHighlighted 


Contained in the ViewController class we will simply setup the newly created desk view. Since we’re utilizing a storyboard file we will override the init(coder:) technique this time, however if you’re instantiating the controller programmatically then you can merely create your individual init technique.


By the way in which I additionally wrapped this view controller inside a navigation controller so I am show a customized title utilizing the big type by default and there are some lacking code items that now we have to write down.


import UIKit

class ViewController: UIViewController 
    
    var tableView: TableView
    
    required init?(coder: NSCoder) 
        self.tableView = TableView(type: .insetGrouped)

        tremendous.init(coder: coder)
    
    
    override func loadView() 
        tremendous.loadView()
        
        view.addSubview(tableView)

        NSLayoutConstraint.activate(tableView.layoutConstraints(in: view))
    

    override func viewDidLoad() 
        tremendous.viewDidLoad()
        
        title = "Desk view"
        navigationController?.navigationBar.prefersLargeTitles = true

        tableView.register(CustomCell.self)
        tableView.delegate = self

    
    
    override func viewDidAppear(_ animated: Bool) 
        tremendous.viewDidAppear(animated)
        
        reload()
    
    
    func reload() 
        
    



extension ViewController: UITableViewDelegate 

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        
    

    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 
        
    


We do not have to implement the desk view knowledge supply strategies, however we will use a diffable knowledge supply for that goal, let me present you the way it works.



Diffable knowledge supply


I’ve already included one instance containing a diffable knowledge supply, however that was a tutorial for creating modern collection views. A diffable knowledge supply is actually a knowledge supply tied to a view, in our case the UITableViewDiffableDataSource generic class goes to behave as a knowledge supply object 4 our desk view. The great take into consideration these knowledge sources is you can simply manipulate the sections and rows contained in the desk view with out the necessity of working with index paths.


So the principle concept right here is that we would wish to show two sections, one with a single choice choice for choosing a quantity, and the second choice group goes to comprise a multi-selection group with some letters from the alphabet. Listed here are the info fashions for the part objects.


enum NumberOption: String, CaseIterable 
    case one
    case two
    case three


extension NumberOption: CustomCellModel 
 
    var textual content: String  rawValue 


enum LetterOption: String, CaseIterable 
    case a
    case b
    case c
    case d


extension LetterOption: CustomCellModel 
 
    var textual content: String  rawValue 


Now we should always be capable to show these things contained in the desk view, if we implement the common knowledge supply strategies, however since we will work with a diffable knowledge supply we’d like some extra fashions. To get rid of the necessity of index paths, we will use a Hashable enum to outline our sections, we will have two sections, one for the numbers and one other for the letters. We’ll wrap the corresponding sort inside an enum with type-safe case values.


enum Part: Hashable 
    case numbers
    case letters


enum SectionItem: Hashable 
    case quantity(NumberOption)
    case letter(LetterOption)


struct SectionData 
    var key: Part
    var values: [SectionItem]


We’re additionally going to introduce a SectionData helper, this manner it will be easier to insert the mandatory sections and part objects utilizing the info supply.


closing class DataSource: UITableViewDiffableDataSource<Part, SectionItem> 
    
    init(_ tableView: UITableView) 
        tremendous.init(tableView: tableView)  tableView, indexPath, itemIdentifier in
            let cell = tableView.reuse(CustomCell.self, indexPath)
            cell.selectionStyle = .none
            change itemIdentifier 
            case .quantity(let mannequin):
                cell.mannequin = mannequin
            case .letter(let mannequin):
                cell.mannequin = mannequin
            
            return cell
        
    
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection part: Int) -> String? 
        let id = sectionIdentifier(for: part)
        change id 
        case .numbers:
            return "Decide a quantity"
        case .letters:
            return "Decide some letters"
        default:
            return nil
        
    

    func reload(_ knowledge: [SectionData], animated: Bool = true) 
        var snapshot = snapshot()
        snapshot.deleteAllItems()
        for merchandise in knowledge 
            snapshot.appendSections([item.key])
            snapshot.appendItems(merchandise.values, toSection: merchandise.key)
        
        apply(snapshot, animatingDifferences: animated)
    


We are able to present a customized init technique for the info supply, the place we will use the cell supplier block to configure our cells with the given merchandise identifier. As you’ll be able to see the merchandise identifier is definitely the SectionItem enum that we created a couple of minutes in the past. We are able to use a change to get again the underlying mannequin, and since these fashions conform to the CustomCellModel protocol we will set the cell.mannequin property. It is usually attainable to implement the common titleForHeaderInSection technique and we will change the part id and return a correct label for every part.


The ultimate technique is a helper, I am utilizing it to reload the info supply with the given part objects.


import UIKit

class ViewController: UIViewController 
    
    var tableView: TableView
    var dataSource: DataSource
    
    required init?(coder: NSCoder) 
        self.tableView = TableView(type: .insetGrouped)
        self.dataSource = DataSource(tableView)

        tremendous.init(coder: coder)
    
    
    override func loadView() 
        tremendous.loadView()
        
        view.addSubview(tableView)

        NSLayoutConstraint.activate(tableView.layoutConstraints(in: view))
    

    override func viewDidLoad() 
        tremendous.viewDidLoad()
        
        title = "Desk view"
        navigationController?.navigationBar.prefersLargeTitles = true

        tableView.register(CustomCell.self)
        tableView.delegate = self

    
    
    override func viewDidAppear(_ animated: Bool) 
        tremendous.viewDidAppear(animated)
        
        reload()
    
    
    func reload() 
        dataSource.reload([
            .init(key: .numbers, values: NumberOption.allCases.map  .number($0) ),
            .init(key: .letters, values: LetterOption.allCases.map  .letter($0) ),
        ])
    



extension ViewController: UITableViewDelegate 

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        
    

    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 
        
    


So contained in the view controller it’s attainable to render the desk view and show each sections, even the cells are selectable by default, however I might like to point out you methods to construct a generic strategy to retailer and return chosen values, after all we might use the indexPathsForSelectedRows property, however I’ve just a little helper instrument which can permit single and a number of choice per part. 🤔


struct SelectionOptions<T: Hashable> 

    var values: [T]
    var selectedValues: [T]
    var multipleSelection: Bool

    init(_ values: [T], chosen: [T] = [], a number of: Bool = false) 
        self.values = values
        self.selectedValues = chosen
        self.multipleSelection = a number of
    

    mutating func toggle(_ worth: T) 
        guard multipleSelection else 
            selectedValues = [value]
            return
        
        if selectedValues.comprises(worth) 
            selectedValues = selectedValues.filter  $0 != worth 
        
        else 
            selectedValues.append(worth)
        
    


Through the use of a generic extension on the UITableViewDiffableDataSource class we will flip the chosen merchandise values into index paths, it will assist us to make the cells chosen when the view hundreds.


import UIKit

extension UITableViewDiffableDataSource 

    func selectedIndexPaths<T: Hashable>(_ choice: SelectionOptions<T>,
                                         _ rework: (T) -> ItemIdentifierType) ->  [IndexPath] 
        choice.values
            .filter  choice.selectedValues.comprises($0) 
            .map  rework($0) 
            .compactMap  indexPath(for: $0) 
    


There is just one factor left to do, which is to deal with the only and a number of choice utilizing the didSelectRowAt and didDeselectRowAt delegate strategies.


import UIKit

class ViewController: UIViewController 
    
    var tableView: TableView
    var dataSource: DataSource
    
    var singleOptions = SelectionOptions<NumberOption>(NumberOption.allCases, chosen: [.two])
    var multipleOptions = SelectionOptions<LetterOption>(LetterOption.allCases, chosen: [.a, .c], a number of: true)

    required init?(coder: NSCoder) 
        self.tableView = TableView(type: .insetGrouped)
        self.dataSource = DataSource(tableView)

        tremendous.init(coder: coder)
    
    
    override func loadView() 
        tremendous.loadView()
        
        view.addSubview(tableView)

        NSLayoutConstraint.activate(tableView.layoutConstraints(in: view))
    

    override func viewDidLoad() 
        tremendous.viewDidLoad()
        
        title = "Desk view"
        navigationController?.navigationBar.prefersLargeTitles = true

        tableView.register(CustomCell.self)
        tableView.delegate = self

    
    
    override func viewDidAppear(_ animated: Bool) 
        tremendous.viewDidAppear(animated)
        
        reload()
    
    
    func reload() 
        dataSource.reload([
            .init(key: .numbers, values: singleOptions.values.map  .number($0) ),
            .init(key: .letters, values: multipleOptions.values.map  .letter($0) ),
        ])

        tableView.choose(dataSource.selectedIndexPaths(singleOptions)  .quantity($0) )
        tableView.choose(dataSource.selectedIndexPaths(multipleOptions)  .letter($0) )
    



extension ViewController: UITableViewDelegate 

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        guard let sectionId = dataSource.sectionIdentifier(for: indexPath.part) else 
            return
        

        change sectionId 
        case .numbers:
            guard case let .quantity(mannequin) = dataSource.itemIdentifier(for: indexPath) else 
                return
            
            tableView.deselectAllInSection(besides: indexPath)
            singleOptions.toggle(mannequin)
            print(singleOptions.selectedValues)
            
        case .letters:
            guard case let .letter(mannequin) = dataSource.itemIdentifier(for: indexPath) else 
                return
            
            multipleOptions.toggle(mannequin)
            print(multipleOptions.selectedValues)
        
    

    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 
        guard let sectionId = dataSource.sectionIdentifier(for: indexPath.part) else 
            return
        
        change sectionId 
        case .numbers:
            tableView.choose([indexPath])
        case .letters:
            guard case let .letter(mannequin) = dataSource.itemIdentifier(for: indexPath) else 
                return
            
            multipleOptions.toggle(mannequin)
            print(multipleOptions.selectedValues)
        
    


Because of this we have created the choice helper strategies to start with of the article. It’s comparatively simple to implement a single and multi-selection part with this system, however after all this stuff are much more easy in the event you can work with SwiftUI.


Anyway, I hope this tutorial helps for a few of you, I nonetheless like UIKit loads and I am glad that Apple provides new options to it. Diffable knowledge sources are glorious approach of configuring desk views and assortment views, with these little helpers you’ll be able to construct your individual settings or picker screens simply. 💪





Source link

Share30Tweet19
learningcode_x1mckf

learningcode_x1mckf

Recommended For You

The abstract Vapor service factory design pattern

by learningcode_x1mckf
February 1, 2023
0
The abstract Vapor service factory design pattern

I've written a number of articles about manufacturing unit design patterns on my weblog and this time I might like to speak a couple of particular one, which...

Read more

SwiftNIO tutorial – The echo server

by learningcode_x1mckf
January 27, 2023
0
SwiftNIO tutorial – The echo server

Intoducing SwiftNIO In the event you used a excessive degree net framework, corresponding to Vapor, up to now, you would possibly had some interplay with occasion loops...

Read more

Introducing – Vapor cheatsheet – The.Swift.Dev.

by learningcode_x1mckf
January 23, 2023
0
Introducing – Vapor cheatsheet – The.Swift.Dev.

Out there on Gumroad Thanks for supporting my work by buying the cheatsheet. 🙏 Download now A whole Vapor framework reference for novices. greater than...

Read more

iCloud Programming Tutorial for iOS: An Introduction

by learningcode_x1mckf
January 18, 2023
0
iCloud Programming Tutorial for iOS: An Introduction

Editor’s observe: This week, we work with Ziad Tamim once more to provide you an introduction of iCloud programming. You’ll learn to save and retrieve knowledge from iCloud.On...

Read more

Easy multipart file upload for Swift

by learningcode_x1mckf
January 18, 2023
0
Easy multipart file upload for Swift

I imagine that you've got already heard in regards to the well-known multipart-data add method that everybody likes to add recordsdata and submit type knowledge, but when not,...

Read more
Next Post
Quick Tip: How to Convert a String to a Number in JavaScript

Quick Tip: How to Convert a String to a Number in JavaScript

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Related News

How to Join JavaScript Strings with a Delimiter Only if the Strings are not Null or Empty? | by John Au-Yeung | Jan, 2023

How to Join JavaScript Strings with a Delimiter Only if the Strings are not Null or Empty? | by John Au-Yeung | Jan, 2023

January 8, 2023
East Java Governor Calls for Acceleration of Booster Shots, Strengthening of Health Protocols

East Java Governor Calls for Acceleration of Booster Shots, Strengthening of Health Protocols

November 6, 2022
An Introduction to JavaScript Web Workers

An Introduction to JavaScript Web Workers

September 19, 2022

Browse by Category

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

RECENT POSTS

  • JobRunr, the Java Scheduler Library, Released Version 6.0 – InfoQ.com
  • An Introduction to Lodash and Its Benefits for JavaScript Developers – MUO – MakeUseOf
  • "Used properly, Python is not slower than C++" – eFinancialCareers (US)

CATEGORIES

  • C#
  • C++
  • Java
  • JavaScript
  • Python
  • Swift

© 2022 Copyright Learning Code

No Result
View All Result
  • Home
  • JavaScript
  • Java
  • Python
  • Swift
  • C++
  • C#

© 2022 Copyright Learning Code

Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?