Skip to content

Commit

Permalink
Merge pull request #1101 from adevinta/component/texteditor
Browse files Browse the repository at this point in the history
[SPA-35-36] Add TextEditor for UIKit
  • Loading branch information
robergro authored Nov 5, 2024
2 parents f93067e + cda47b6 commit aede447
Show file tree
Hide file tree
Showing 8 changed files with 460 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .Demo/Classes/Enum/UIComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct UIComponent: RawRepresentable, CaseIterable, Equatable {
.switch,
.tab,
.tag,
.textEditor,
.textField,
.textFieldAddons,
.textLink
Expand Down Expand Up @@ -60,6 +61,7 @@ struct UIComponent: RawRepresentable, CaseIterable, Equatable {
static let star = UIComponent(rawValue: "Star")
static let `switch` = UIComponent(rawValue: "Switch")
static let tab = UIComponent(rawValue: "Tab")
static let textEditor = UIComponent(rawValue: "TextEditor")
static let tag = UIComponent(rawValue: "Tag")
static let textField = UIComponent(rawValue: "TextField")
static let textFieldAddons = UIComponent(rawValue: "TextFieldAddons")
Expand Down
2 changes: 2 additions & 0 deletions .Demo/Classes/View/Components/ComponentsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ extension ComponentsViewController {
viewController = TabComponentUIViewController.build()
case .tag:
viewController = TagComponentUIViewController.build()
case .textEditor:
viewController = TextEditorComponentUIViewController.build()
case .textField:
viewController = TextFieldComponentUIViewController.build()
case .textFieldAddons:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//
// TextEditorComponentUIView.swift
// SparkDemo
//
// Created by alican.aycil on 12.06.24.
// Copyright © 2024 Adevinta. All rights reserved.
//

import Combine
import SparkCore
import UIKit
@_spi(SI_SPI) import SparkCommon

final class TextEditorComponentUIView: ComponentUIView, UIGestureRecognizerDelegate {

// MARK: - Components

private var componentView: TextEditorUIView

// MARK: - Properties

private let viewModel: TextEditorComponentUIViewModel
private var cancellables: Set<AnyCancellable> = []

private var widthLayoutConstraint: NSLayoutConstraint
private var heightLayoutConstraint: NSLayoutConstraint

// MARK: - Initializer

init(viewModel: TextEditorComponentUIViewModel) {
self.viewModel = viewModel
self.componentView = Self.makeTextEditorView(viewModel)

// Constraints
self.widthLayoutConstraint = self.componentView.widthAnchor.constraint(equalToConstant: 300)
self.heightLayoutConstraint = self.componentView.heightAnchor.constraint(equalToConstant: 100)

super.init(
viewModel: viewModel,
componentView: self.componentView
)

// Setup
let tap = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTapped))
tap.delegate = self
addGestureRecognizer(tap)

self.setupSubscriptions()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Subscribe
private func setupSubscriptions() {
self.viewModel.$theme.subscribe(in: &self.cancellables) { [weak self] theme in
guard let self = self else { return }
let themes = self.viewModel.themes
let themeTitle: String? = theme is SparkTheme ? themes.first?.title : themes.last?.title

self.viewModel.themeConfigurationItemViewModel.buttonTitle = themeTitle
self.viewModel.configurationViewModel.update(theme: theme)

self.componentView.theme = theme
}

self.viewModel.$intent.subscribe(in: &self.cancellables) { [weak self] intent in
guard let self = self else { return }
self.viewModel.intentConfigurationItemViewModel.buttonTitle = intent.name
self.componentView.intent = intent
}

self.viewModel.$text.subscribe(in: &self.cancellables) { [weak self] type in
guard let self = self else { return }
self.viewModel.textConfigurationItemViewModel.buttonTitle = type.name
self.componentView.text = Self.setText(type: type)
}

self.viewModel.$placeholder.subscribe(in: &self.cancellables) { [weak self] type in
guard let self = self else { return }
self.viewModel.placeholderConfigurationItemViewModel.buttonTitle = type.name
self.componentView.placeholder = Self.setText(type: type)
}

self.viewModel.$isEnabled.subscribe(in: &self.cancellables) { [weak self] isEnabled in
guard let self = self else { return }
self.componentView.isEnabled = isEnabled
}

self.viewModel.$isEditable.subscribe(in: &self.cancellables) { [weak self] isEditable in
guard let self = self else { return }
self.componentView.isEditable = isEditable
}

self.viewModel.$isStaticSizes.subscribe(in: &self.cancellables) { [weak self] isStaticSizes in
guard let self = self else { return }
self.widthLayoutConstraint.isActive = isStaticSizes
self.heightLayoutConstraint.isActive = isStaticSizes
self.componentView.isScrollEnabled = isStaticSizes
}
}

static private func makeTextEditorView(_ viewModel: TextEditorComponentUIViewModel) -> TextEditorUIView {
let view = TextEditorUIView(
theme: viewModel.theme,
intent: viewModel.intent
)
view.text = TextEditorComponentUIView.setText(type: viewModel.text)
view.placeholder = TextEditorComponentUIView.setText(type: viewModel.placeholder)
view.isEnabled = viewModel.isEnabled
view.isEditable = viewModel.isEditable
view.isScrollEnabled = viewModel.isStaticSizes
return view
}

static private func setText(type: TextEditorContent) -> String {
switch type {
case .none:
return ""
case .short:
return "What is Lorem Ipsum?"
case .medium:
return "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
case .long:
return "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."
}
}

@objc private func viewDidTapped() {
_ = self.componentView.resignFirstResponder()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// TextEditorComponentUIViewController.swift
// SparkDemo
//
// Created by alican.aycil on 29.05.24.
// Copyright © 2024 Adevinta. All rights reserved.
//

import Combine
import SparkCore
import SwiftUI
import UIKit
@_spi(SI_SPI) import SparkCommon

final class TextEditorComponentUIViewController: UIViewController {

// MARK: - Published Properties
@ObservedObject private var themePublisher = SparkThemePublisher.shared

// MARK: - Properties
let componentView: TextEditorComponentUIView
let viewModel: TextEditorComponentUIViewModel
private var cancellables: Set<AnyCancellable> = []

// MARK: - Initializer
init(viewModel: TextEditorComponentUIViewModel) {
self.viewModel = viewModel
self.componentView = TextEditorComponentUIView(viewModel: viewModel)
super.init(nibName: nil, bundle: nil)

self.componentView.viewController = self
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Lifecycle
override func loadView() {
super.loadView()
view = componentView
}

// MARK: - ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "TextEditor"
addPublisher()
}

// MARK: - Add Publishers
private func addPublisher() {

self.themePublisher
.$theme
.sink { [weak self] theme in
guard let self = self else { return }
self.viewModel.theme = theme
self.navigationController?.navigationBar.tintColor = theme.colors.main.main.uiColor
}
.store(in: &self.cancellables)

self.viewModel.showThemeSheet.subscribe(in: &self.cancellables) { intents in
self.presentThemeActionSheet(intents)
}

self.viewModel.showIntentSheet.subscribe(in: &self.cancellables) { intents in
self.presentIntentActionSheet(intents)
}

self.viewModel.showTextSheet.subscribe(in: &self.cancellables) { types in
self.presentTextActionSheet(types)
}

self.viewModel.showPlaceholderSheet.subscribe(in: &self.cancellables) { types in
self.presentPlaceholdderActionSheet(types)
}
}
}

// MARK: - Builder
extension TextEditorComponentUIViewController {

static func build() -> TextEditorComponentUIViewController {
let viewModel = TextEditorComponentUIViewModel(theme: SparkThemePublisher.shared.theme)
return TextEditorComponentUIViewController(viewModel: viewModel)
}
}

// MARK: - Navigation
extension TextEditorComponentUIViewController {

private func presentThemeActionSheet(_ themes: [ThemeCellModel]) {
let actionSheet = SparkActionSheet<Theme>.init(
values: themes.map { $0.theme },
texts: themes.map { $0.title }) { theme in
self.themePublisher.theme = theme
}
self.present(actionSheet, isAnimated: true)
}

private func presentIntentActionSheet(_ intents: [TextEditorIntent]) {
let actionSheet = SparkActionSheet<TextEditorIntent>.init(
values: intents,
texts: intents.map { $0.name }) { intent in
self.viewModel.intent = intent
}
self.present(actionSheet, isAnimated: true)
}

private func presentTextActionSheet(_ types: [TextEditorContent]) {
let actionSheet = SparkActionSheet<TextEditorContent>.init(
values: types,
texts: types.map{ $0.name }) { text in
self.viewModel.text = text
}
self.present(actionSheet, isAnimated: true)
}

private func presentPlaceholdderActionSheet(_ types: [TextEditorContent]) {
let actionSheet = SparkActionSheet<TextEditorContent>.init(
values: types,
texts: types.map{ $0.name }) { text in
self.viewModel.placeholder = text
}
self.present(actionSheet, isAnimated: true)
}
}
Loading

0 comments on commit aede447

Please sign in to comment.