iOS

Custom Image Row using Eureka in iOS

Image row is a component which emerges usually when building form using eureka in iOS. There is a library implement image row but it is not customizable. Therefore I write this post to explain how to implement and customize image row to suit your needs.

Contents

Set up view in xib file

In Xcode menu click File > New > File > Cocoa Touch Class. In the dialog type name of the class as CustomImageCell:

Create UITableViewCell with name CustomImageCell
Create UITableViewCell with name CustomImageCell

Click Next > Create. You’ll see there is 2 file CustomImageCell.swift and CustomImageCell.xib added to Xcode.

Oen File CustomImageCell.xib, insert an UIImageView and UIButton, or you can copy content from the demo source.

Layout CustomImageCell.xib
Layout CustomImageCell.xib

Set up class CustomImageCell

Open file CustomImageCell.swift and update content of class CustomImageCell as follow:

import Eureka

public class CustomImageCell: Cell<UIImage>, CellType {
    @IBOutlet weak var thumbnailView: UIImageView!
    
    override public func update() {
        super.update()
        thumbnailView.image = row.value
    }
    
    @IBAction func imageClicked(_ sender: Any) {
        row.didSelect()
    }
}

Next open file CustomImageCell.xib and connect IBAction of UIButton to method imageClicked like this:

Connect IBAction
Connect IBAction

Set up class CustomImageRow

In file CustomImageCell.swift, add the following code:

public final class CustomImageRow: Row<CustomImageCell>, RowType {
    required public init(tag: String?) {
        super.init(tag: tag)
        // We set the cellProvider to load the .xib corresponding to our cell
        cellProvider = CellProvider<CustomImageCell>(nibName: "CustomImageCell", bundle: Bundle.main)
    }
    
    public override func customDidSelect() {
        super.customDidSelect()
        
        let picker = UIImagePickerController()
        picker.delegate = self
        cell.formViewController()?.present(picker, animated: true, completion: nil)
    }
}

extension CustomImageRow: UIImagePickerController, UINavigationControllerDelegate {
    public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
    
    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.originalImage] as? UIImage {
            value = image
            updateCell()
            picker.dismiss(animated: true, completion: nil)
        }
    }
}

There are 2 things need to noted when using Eureka:

  • Cell only does one job that is display UI and receive event UI.
  • Logic and remaining tasks will be handled by Row

Handle callback of UIImagePickerController

If you run the project by now, you’ll see Xcode shows the following error:

Type 'CustomImageRow' does not conform to protocol 'NSObjectProtocol'.
Do you want to add protocol stubs?

To fix this problem, we’re gonna create an intermediate class which inherits from NSObject then forward callback back to CustomImageRow.

Create class PickerPropagator with the following content:

import Foundation
import UIKit

protocol PickerPropagatorDelegate: AnyObject {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
}

class PickerPropagator: NSObject, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    weak var delegate: PickerPropagatorDelegate?
    
    public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        delegate?.imagePickerControllerDidCancel(picker)
    }
    
    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        delegate?.imagePickerController(picker, didFinishPickingMediaWithInfo: info)
    }
}

In class CustomImageRow, add property pickerPropagator to forward callback from UIImagePickerController by changing CustomImageRow to the following:

public final class CustomImageRow: Row<CustomImageCell>, RowType {
    private let pickerPropagator: PickerPropagator = PickerPropagator()
    
    required public init(tag: String?) {
        super.init(tag: tag)
        // We set the cellProvider to load the .xib corresponding to our cell
        cellProvider = CellProvider<CustomImageCell>(nibName: "CustomImageCell", bundle: Bundle.main)
        pickerPropagator.delegate = self
    }
    
    public override func customDidSelect() {
        super.customDidSelect()
        
        let picker = UIImagePickerController()
        picker.delegate = pickerPropagator
        cell.formViewController()?.present(picker, animated: true, completion: nil)
    }
}

extension CustomImageRow: PickerPropagatorDelegate {
    public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
    
    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.originalImage] as? UIImage {
            value = image
            updateCell()
            picker.dismiss(animated: true, completion: nil)
        }
    }
}

By now the implementation of custom image row is completed. Next we’ll add it to the form.

Integrate CustomImageRow into view controller

In class ViewController, add the following code in viewDidLoad:

form +++ Section("Section1")
    <<< CustomImageRow() { row in
    }.cellSetup({ (cell, row) in
        cell.height = { 100 }
    })

Next, add the following code at the end of class ViewController to prevent tap outside of UIButton:

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    if tableView.cellForRow(at: indexPath) is CustomImageCell {
        return nil
    }
    return super.tableView(tableView, willSelectRowAt: indexPath)
}

Now you can run the project and try the demo.

Wraps Up

Here are keypoints when implementing custom image row:

  • Cell only does one job that is display UI and receive event UI.
  • Logic and remaining tasks will be handled by Row.
  • Use intermediate class to forward callback if Row cannot handle directly.

Hope this post could be useful to you. If there is anything please let me know in the comment section. Have a nice day!

Leave a Reply

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