-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1101 from adevinta/component/texteditor
[SPA-35-36] Add TextEditor for UIKit
- Loading branch information
Showing
8 changed files
with
460 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
.Demo/Classes/View/Components/TextEditor/UIKit/TextEditorComponentUIView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
.Demo/Classes/View/Components/TextEditor/UIKit/TextEditorComponentUIViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.