Customize View using ViewFactory

ViewFactory customisations are currently not available for React Native.

In the following sections, we will discuss the UIKit ViewFactory and how to use it to customize various IQL flows:

How ViewFactory Customizations Work

Customize Views

How ViewFactory Customizations Work

The ZendriveIQLUIKitViewFactory is a class that allows users to customize the appearance and behavior of various views of the IQL flow. ZendriveUIKit will ask the View Factory to create customized views and will share the data with the View object to be used in UI creation/updation.

Default implementation of all views is available in UIKit and can be copied for quicker customization. To create a default view, simply instantiate a component – for example, CombinedProgramSummaryWidget(). This action alone generates a component with the default view.

Key Components

  • Default Implementations: The ZendriveIQLUIKitViewFactory includes default implementations for creating views, such as CombinedProgramSummaryWidget and DashboardScreen. These default implementations serve as templates that users can customize to meet their needs.

  • Customization: Users can extend the ZendriveIQLUIKitViewFactory class and override specific methods to provide their own implementations of views. This customization enables developers to modify the appearance, behavior, and functionality of views according to their application's requirements.

Customize Views

Extend ZendriveIQLUIKitViewFactory

To begin customizing views, developers should create their own class that extends the ZendriveIQLUIKitViewFactory:

class CustomViewFactory : ZendriveIQLUIKitViewFactory() {
    // Override methods to customize views
}

Override Methods

Within the custom ZendriveIQLUIKitViewFactory, users can override methods corresponding to the views they want to customize. For example, to customize the CombinedProgramSummaryWidget, users need to override the createCombinedProgramSummaryWidget method:

iOS (Swift)
class CustomViewFactory: ZendriveIQLUIKitViewFactory {

    func createCombinedProgramSummaryWidget() -> BaseCombinedProgramSummaryWidget {
        return CustomCombinedProgramSummaryWidget()
    }
}
Android (Kotlin)
class CustomViewFactory : ZendriveIQLUIKitViewFactory() {

    override fun createCombinedProgramSummaryWidgetView(context: Context): CombinedProgramSummaryWidgetView {
        return CustomCombinedProgramSummaryWidgetView(context)
    }
}

Provide Custom Implementations

Developers should provide their own implementations of views that adhere to the base classes defined in ZendriveIQLUIKit.

For instance, in iOS if an app requires a distinct appearance or added functionality for the CombinedProgramSummaryWidget, developers can create a custom view that implements the BaseCombinedProgramSummaryWidget base class and for Android they can extend CombinedProgramSummaryWidgetView

Below is an example of how developers can customize the CombinedProgramSummaryWidget

iOS (Swift)
class CustomCombinedProgramSummaryWidget: BaseCombinedProgramSummaryWidget {
    private var bag = Set<AnyCancellable>()

    static let typography = CustomTypography()
    static let theme = DefaultZendriveIQLUIKitLightTheme(colorSet: CustomColorSet())

    public var state: CombinedProgramSummaryWidgetState? {
        didSet {
            guard let state = state else { return }
            smallImageView.image = UIImage(named: state.smallImage)
            bigImageView.image = UIImage(named: state.bigImage)
            valuePropTextView.text = state.valueProp
            titleView.text = state.title
            messageTextView.text = state.message
            primaryButton.setTitle(state.primaryButtonTitle, for: .normal)
            if let _ = state.secondaryButtonTitle {
                secondaryButton.setTitle(state.secondaryButtonTitle, for: .normal)
                secondaryButton.isHidden = false
            } else {
                secondaryButton.isHidden = true
            }
        }
    }

    private var smallImageView: UIImageView = {
        let imageview = UIImageView(frame: .zero)
        imageview.translatesAutoresizingMaskIntoConstraints = false
        imageview.contentMode = .scaleAspectFit
        return imageview
    }()

    private var valuePropTextView: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = typography.title3Bold
        label.textColor = theme.textPrimary
        label.textAlignment = .left
        return label
    }()

    private var bigImageView: UIImageView = {
        let imageview = UIImageView(frame: .zero)
        imageview.translatesAutoresizingMaskIntoConstraints = false
        return imageview
    }()

    private var titleView: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = typography.title3Bold
        label.textColor = theme.textPrimary
        label.textAlignment = .left
        return label
    }()

    private var messageTextView: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = typography.bodyRegular
        label.textColor = theme.textBody
        label.numberOfLines = 0
        label.textAlignment = .left
        return label
    } ()

    public var primaryButton: UIButton = {
        let button = IQLUIKitPrimaryButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
        return button
    }()

    public var secondaryButton: UIButton = {
        let button = IQLUIKitSecondaryButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required public init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
    }

    private func setupViews() {
        backgroundColor = Self.theme.cardPrimaryBg
        translatesAutoresizingMaskIntoConstraints = false
        addSubview(smallImageView)
        NSLayoutConstraint.activate([
            smallImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16),
            smallImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8)
        ])

        addSubview(valuePropTextView)
        NSLayoutConstraint.activate([
            valuePropTextView.leftAnchor.constraint(equalTo: smallImageView.rightAnchor, constant: 12),
            valuePropTextView.centerYAnchor.constraint(equalTo: smallImageView.centerYAnchor, constant: 0),
            valuePropTextView.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -12)
        ])

        addSubview(bigImageView)
        NSLayoutConstraint.activate([
            bigImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0),
            bigImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0),
            bigImageView.topAnchor.constraint(equalTo: smallImageView.bottomAnchor, constant: 8)
        ])

        addSubview(titleView)
        NSLayoutConstraint.activate([
            titleView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16),
            titleView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16),
            titleView.topAnchor.constraint(equalTo: bigImageView.bottomAnchor, constant: 8)
        ])

        addSubview(messageTextView)
        NSLayoutConstraint.activate([
            messageTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16),
            messageTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16),
            messageTextView.topAnchor.constraint(equalTo: titleView.bottomAnchor, constant: 8)
        ])

        addSubview(primaryButton)
        addSubview(secondaryButton)

        NSLayoutConstraint.activate([
            primaryButton.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0),
            primaryButton.topAnchor.constraint(equalTo: messageTextView.bottomAnchor, constant: 20),
            secondaryButton.centerXAnchor.constraint(equalTo: centerXAnchor),
            secondaryButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
            secondaryButton.topAnchor.constraint(equalTo: primaryButton.bottomAnchor, constant: 30),
        ])

        primaryButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)

        primaryButton.publisher(for: .touchUpInside)
            .sink { [weak self] _ in
                self?.viewListener?.onPrimaryButtonClicked()
            }
            .store(in: &bag)

        secondaryButton.publisher(for: .touchUpInside)
            .sink { [weak self] _ in
                self?.viewListener?.onSecondaryButtonClicked()
            }
            .store(in: &bag)
    }

    public override func setState(_ state: CombinedProgramSummaryWidgetState) {
        self.state = state
    }

    public override func getState() -> CombinedProgramSummaryWidgetState? {
        return state
    }
}
Android (Kotlin)
class CustomCombinedProgramSummaryWidgetView(context: Context): CombinedProgramSummaryWidgetView(context) {
    override var bigImageView: ImageView? = null
    override var messageTextView: TextView? = null
    override var primaryButton: Button? = null
    override var secondaryButton: Button? = null
    override var smallImageView: ImageView? = null
    override var titleTextView: TextView? = null
    override var valuePropTextView: TextView? = null

    init {
        val binding = CustomCombinedProgramSummaryWidgetViewBinding.inflate(
            LayoutInflater.from(context), this, true
        )
        bigImageView = binding.bigImageView
        primaryButton = binding.primaryButton
        secondaryButton = binding.secondaryButton
        titleTextView = binding.titleTextView
        messageTextView = null
        smallImageView = null
        valuePropTextView = null
        
        super.initialize()
    }
    
    override fun setState(state: CombinedProgramSummaryWidgetViewState?) {
        super.setState(state)
        
        // Add customizations here based on state
    }
}

// custom_combined_program_summary_widget_view.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    tools:background="#ffffff">

    <ImageView
        android:id="@+id/bigImageView"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginVertical="@dimen/dp8"
        android:layout_marginStart="@dimen/dp16"
        android:src="@drawable/ziu_program_introduction_screen_big_image"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/titleTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/ziu_opt_in_card_title"
        android:layout_marginLeft="20dp"
        android:textColor="#000000"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/bigImageView"
        app:layout_constraintLeft_toRightOf="@id/bigImageView"/>

    <Button
        android:id="@+id/primaryButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:text="@string/ziu_opt_in_card_button_text"
        app:layout_constraintLeft_toRightOf="@id/bigImageView"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginRight="20dp"
        app:layout_constraintTop_toBottomOf="@id/titleTextView"/>

    <Button
        android:id="@+id/secondaryButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginRight="20dp"
        android:text="@string/ziu_opt_in_card_button_text"
        app:layout_constraintLeft_toRightOf="@id/bigImageView"
        app:layout_constraintTop_toBottomOf="@+id/primaryButton"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Set View Factory in ZendriveIQLUIKit

To use the custom ZendriveIQLUIKitViewFactory, developers must instantiate it and pass it to ZendriveIQLUIKit. Here is how to set it to UIKit:

ZendriveIQLUIKit.setViewFactory(viewFactory: CustomViewFactory())