Skip to content

YamamotoDesu/UITableViewDiffableDataSource

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UITableViewDiffableDataSource-Swift

platforms

Empty Registration List Info Routing Reviewing

Context

A diffable data source object is a specialized type of data source that works together with your table view object. It provides the behavior you need to manage updates to your table view’s data and UI in a simple, efficient way.
https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource

Requirement

iPhone Image

Gif image Standard Dark

iPad Image

Standard Dark

Sourcecode

Modern UITable View

To connect a diffable data source to a table view(iOS 13 and onward)

    lazy var dataSource = configureDataSource()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Assign the diffable data source to your table view.
        tableView.dataSource = dataSource
        
        //Generate the current state of the table data by creating a snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections([.all])
        snapshot.appendItems(restaurantNames, toSection: .all)
        
        //Call the apply() function of the data source to populate the data
        dataSource.apply(snapshot, animatingDifferences: false)
        
        
    }
    
    func configureDataSource() -> UITableViewDiffableDataSource<Section, String> {
        let cellIdentifier = "favoritecell"
        //Create a UITableViewDiffableDataSource object to connect with your table andprovide the configuration of the table view cells.
        let dataSource = UITableViewDiffableDataSource<Section, String>(
            tableView: tableView,
            cellProvider: { tableView, indexPath, restaurantName in
                let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RestaurantTableViewCell
                cell.nameLabel.text = restaurantName
                cell.thumbnailImageView.image = UIImage(named: self.restaurantImages[indexPath.row])
                cell.locationLabel.text = self.restaurantLocations[indexPath.row]
                cell.typeLabel.text = self.restaurantTypes[indexPath.row]
                cell.heartMark.isHidden = !self.restaurantIsFavorites[indexPath.row]
                cell.heartMark.tintColor = UITraitCollection.isDarkMode ? .systemYellow : .blue
                return cell
                
            }
        )
        return dataSource
        
    }

Functionality

Routing With MapKit

https://github.com/YamamotoDesu/UITableViewDiffableDataSource-Swift/blob/main/FoodPin/Controller/MapViewController.swift

Locating Routing

Animation

StandardAnimation SpringAnimation
    override func viewDidLoad() {
        super.viewDidLoad()
        let moveScaleTransform = getScaleAnimation()
        
        // Make the button invisible
        for rateButton in rateButtons {
            rateButton.transform = moveScaleTransform
            rateButton.alpha = 0
        }
    }
    
    func getScaleAnimation() -> CGAffineTransform {
        let moveRightTransform = CGAffineTransform.init(translationX: 600, y: 0)
        let scaleUpTransform = CGAffineTransform.init(scaleX: 5.0, y: 5.0)
        return scaleUpTransform.concatenating(moveRightTransform)
    }
    
    func setSpringAnimation() {
        
        var delay: TimeInterval = 0.1
        for rateButton in self.rateButtons {
            UIView.animate(withDuration: 0.8, delay: delay, usingSpringWithDamping: 0.2,
                           initialSpringVelocity: 0.3, options: [], animations
                            :{
                                rateButton.alpha = 1.0
                                rateButton.transform = .identity
                            }, completion: nil)
            
            delay += 0.1
        }
        UIView.animate(withDuration: 0.4, delay: 0.1, options: [], animations:
                        {
                            self.closeButton.alpha = 1.0
                        }, completion: nil)
    }
    
    func setStandardAnimation() {
        var delay: TimeInterval = 0.1
        for rateButton in self.rateButtons {
            UIView.animate(withDuration: 0.4, delay: delay, options: [], animations:
                            {
                                rateButton.alpha = 1.0
                                rateButton.transform = .identity
                            }, completion: nil)
            
            delay += 0.1
        }
        
        UIView.animate(withDuration: 0.4, delay: 0.1, options: [], animations:
                        {
                            self.closeButton.alpha = 1.0
                        }, completion: nil)

    }

Importing photos with UIImagePickerController

https://github.com/YamamotoDesu/UITableViewDiffableDataSource-Swift/blob/main/FoodPin/Controller/NewRestaurantController.swift

extension NewRestaurantController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        if let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            photoImageView.image = selectedImage
            photoImageView.contentMode = .scaleAspectFit
            photoImageView.clipsToBounds = true
        }
        
        dismiss(animated: true, completion: nil)
    }
}

Returns the swipe actions to display on the leading edge of the row(iOS 11 and onward)

        
    override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        // Get the selected restaurant
        guard let restaurant = self.dataSource.itemIdentifier(for: indexPath) else {
            return UISwipeActionsConfiguration()
        }
        
        // Favorite action
        let favoriteAction = UIContextualAction(style: .normal, title: "") { (action, sourceView, completionHandler) in
            
            let cell = tableView.cellForRow(at: indexPath) as! RestaurantTableViewCell
            cell.heartMark.isHidden = !restaurant.isFavorite
            cell.heartMark.tintColor = UITraitCollection.isDarkMode ? .systemYellow : .blue
            self.restaurantIsFavorites[indexPath.row] = !restaurant.isFavorite
            tableView.reloadData()
            
            // Call completion handler to dismiss tbe action button
            completionHandler(true)
        }
        
        favoriteAction.backgroundColor = UIColor.systemYellow
        favoriteAction.image = UIImage(systemName: "heart.fill")
        
        let swipeConfiguration = UISwipeActionsConfiguration(actions: [favoriteAction])
        return swipeConfiguration
    }

Returns the swipe actions to display on the trailing edge of the row(iOS 11 and onward)

    override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        // Get the selected restaurant
        guard let restaurant = self.dataSource.itemIdentifier(for: indexPath) else {
            return UISwipeActionsConfiguration()
        }
        
        // Delete action
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (action, sourceView, completionHandler) in
            
            var snapshot = self.dataSource.snapshot()
            snapshot.deleteItems([restaurant])
            self.dataSource.apply(snapshot, animatingDifferences: true)
            
            // Call completion handler to dismiss tbe action button
            completionHandler(true)
        }
        
        //Share action
        let shareAction = UIContextualAction(style: .normal, title: "Share") { (action, sourceView, completionHandler) in
            let defaultText = "Just checking in at " + restaurant.name
            
            let activityController: UIActivityViewController
            
            if let imageToShare = UIImage(named: restaurant.image) {
                activityController = UIActivityViewController(activityItems: [defaultText, imageToShare], applicationActivities: nil)
            } else {
                activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            }
            
            if let popoverController = activityController.popoverPresentationController {
                if let cell = tableView.cellForRow(at: indexPath) {
                    popoverController.sourceView = cell
                    popoverController.sourceRect = cell.bounds
                    
                }
            }
            
            self.present(activityController, animated: true, completion: nil)
            completionHandler(true)
        }
        
        deleteAction.backgroundColor = UIColor.systemRed
        deleteAction.image = UIImage(systemName: "trash")
        
        shareAction.backgroundColor = UIColor.systemOrange
        shareAction.image = UIImage(systemName: "square.and.arrow.up")
        
        // Configure both actions as swipe action
        let swipeConfiguration = UISwipeActionsConfiguration(actions: [deleteAction, shareAction])
        return swipeConfiguration
    }

Something notes

In response to dynamic type changes, change cell dynamically

Reference: Apple's iOS Human Interface Guidelines
https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/

Gif image Standard Large
 override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        
        // perform action here when user changes the text size
        switch self.traitCollection.preferredContentSizeCategory {
        case .extraExtraLarge, .extraExtraExtraLarge, .accessibilityExtraLarge, .accessibilityExtraExtraLarge, .accessibilityExtraExtraExtraLarge:
            self.isDynamicLargeType = true
        case .extraSmall, .small, .medium, .accessibilityMedium, .large, .extraLarge:
            self.isDynamicLargeType = false
        default:
            self.isDynamicLargeType = false
        }
        
        // reload view here when user changes the text size and change cell identifier
        if previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory {
            self.dataSource = configureDataSource()
            self.viewDidLoad()
        }
        
    }

Set Transparent navigation bar

Before Before After After
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.hidesBarsOnSwipe = true
        if #available(iOS 14.0, *) {
          navigationItem.backButtonDisplayMode = .minimal
        } else {
          navigationItem.backButtonTitle = " "
        }
    
        // To customize the navigation bar, you first need to retrieve the currentUINavigationBarAppearance object
        // The standardAppearance property contains thecurrent appearance settings for the standard size navigation bar
        if let appearance = navigationController?.navigationBar.standardAppearance {
            appearance.configureWithTransparentBackground()
            if let customFont = UIFont(name: "Nunito-Bold", size: 40.0) {

                appearance.titleTextAttributes = [.foregroundColor:
                                                    UIColor(named: "NavigationBarTitle")!, .font: customFont]
                appearance.largeTitleTextAttributes = [.foregroundColor:
                                                        UIColor(named: "NavigationBarTitle")!, .font: customFont]
            }

            navigationController?.navigationBar.standardAppearance = appearance
            navigationController?.navigationBar.compactAppearance = appearance
            navigationController?.navigationBar.scrollEdgeAppearance = appearance
    }
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        let navBarAppearence = UINavigationBarAppearance()
        
        var backButtonImage = UIImage(systemName: "arrow.backword", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20.0, weight: .bold))
        backButtonImage = backButtonImage?.withAlignmentRectInsets(UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0))
        
        UINavigationBar.appearance().tintColor = .white
        UINavigationBar.appearance().standardAppearance = navBarAppearence
        UINavigationBar.appearance().compactAppearance = navBarAppearence
        UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearence
       
        return true
    }
    

To prevent content from becoming overly wide for iPad

tableView.cellLayoutMarginsFollowReadableWidth = true
iPad Image
tableView.cellLayoutMarginsFollowReadableWidth = false tableView.cellLayoutMarginsFollowReadableWidth = true

Check current userinterfacestyle programmatically

//-------acronym--------------
    // Called when the iOS interface environment changes.
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
      super.traitCollectionDidChange(previousTraitCollection)

        self.heartMark.tintColor = UITraitCollection.isDarkMode ? .systemYellow : .systemPink
    }
//---------------------


extension UITraitCollection {

    public static var isDarkMode: Bool {
        if #available(iOS 13, *), current.userInterfaceStyle == .dark {
            return true
        }
        return false
    }
}

Set custom font programmatically

    @IBOutlet var typeLabel: UILabel! {
        didSet {
            if let cutomFont = UIFont(name: "Nunito-Regular", size: 20.0) {
                typeLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: cutomFont)
            }
        }
    }
Setting Font Image