diff --git a/CHANGELOG.md b/CHANGELOG.md index b4dc634e..7df67573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - **Added**: `BaseInitializableWebView`with navigation and error handling api. +### 1.31.0 + +- **Added**: `URLInteractiveTextView` for terms and conditions hints in login flow + ### 1.30.0 - **Added**: Base classes for encryption and decryption user token with pin code or biometry diff --git a/TILogging/Sources/Views/LoggerList/LogsListViewController.swift b/TILogging/Sources/Views/LoggerList/LogsListViewController.swift index 7168311a..313e348e 100644 --- a/TILogging/Sources/Views/LoggerList/LogsListViewController.swift +++ b/TILogging/Sources/Views/LoggerList/LogsListViewController.swift @@ -26,7 +26,7 @@ import OSLog import UIKit @available(iOS 15, *) -open class LogsListViewController: BaseInitializeableViewController, +open class LogsListViewController: BaseInitializableViewController, LogsListViewOutput, AlertPresentationContext, UISearchBarDelegate, diff --git a/TILogging/Sources/Views/LoggerWindow/LoggingTogglingViewController.swift b/TILogging/Sources/Views/LoggerWindow/LoggingTogglingViewController.swift index 5a03f6fc..9fbafcf7 100644 --- a/TILogging/Sources/Views/LoggerWindow/LoggingTogglingViewController.swift +++ b/TILogging/Sources/Views/LoggerWindow/LoggingTogglingViewController.swift @@ -24,7 +24,7 @@ import TIUIKitCore import UIKit @available(iOS 15, *) -open class LoggingTogglingViewController: BaseInitializeableViewController { +open class LoggingTogglingViewController: BaseInitializableViewController { private var initialCenter = CGPoint() diff --git a/TIUIElements/Sources/Views/BaseInitializableTextView.swift b/TIUIElements/Sources/Views/BaseInitializableTextView.swift new file mode 100644 index 00000000..c0aa13db --- /dev/null +++ b/TIUIElements/Sources/Views/BaseInitializableTextView.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UITextView +import TIUIKitCore + +open class BaseInitializableTextView: UITextView, InitializableViewProtocol { + public override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + + initializeView() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + initializeView() + } + + // MARK: - InitializableView + + open func addViews() { + // override in subclass + } + + open func configureLayout() { + // override in subclass + } + + open func bindViews() { + // override in subclass + } + + open func configureAppearance() { + // override in subclass + } + + open func localize() { + // override in subclass + } +} diff --git a/TIUIElements/Sources/Views/URLInteractiveTextView/DefaultUITextViewURLInteractionHandler.swift b/TIUIElements/Sources/Views/URLInteractiveTextView/DefaultUITextViewURLInteractionHandler.swift new file mode 100644 index 00000000..1b3546d9 --- /dev/null +++ b/TIUIElements/Sources/Views/URLInteractiveTextView/DefaultUITextViewURLInteractionHandler.swift @@ -0,0 +1,42 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UITextView +import TISwiftUtils + +open class DefaultUITextViewURLInteractionHandler: NSObject, UITextViewURLInteractionHandler { + public var urlInteractionHandler: ParameterClosure? + + public init(urlInteractionHandler: ParameterClosure?) { + self.urlInteractionHandler = urlInteractionHandler + } + + open func textView(_ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction) -> Bool { + + urlInteractionHandler?(URL) + + return false + } +} diff --git a/TIUIElements/Sources/Views/URLInteractiveTextView/UITextView+InteractiveParts.swift b/TIUIElements/Sources/Views/URLInteractiveTextView/UITextView+InteractiveParts.swift new file mode 100644 index 00000000..e02b9475 --- /dev/null +++ b/TIUIElements/Sources/Views/URLInteractiveTextView/UITextView+InteractiveParts.swift @@ -0,0 +1,67 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UITextView +import TIUIKitCore + +public extension UITextView { + private typealias InteractiveRange = (range: Range, url: URL) + + struct InteractivePart { + public let text: String + public let url: URL? + + public init(text: String, url: URL?) { + self.text = text + self.url = url + } + } + + func set(text: String, + primaryTextStyle: BaseTextAttributes, + interactiveParts: [InteractivePart], + interactivePartsStyle: BaseTextAttributes) { + + let mutableAttributedText = NSMutableAttributedString(string: text, attributes: primaryTextStyle.attributedStringAttributes) + + let interactiveAttributes = interactivePartsStyle.attributedStringAttributes + + interactiveParts + .compactMap { interactivePart -> InteractiveRange? in + guard let range = text.range(of: interactivePart.text), + let url = interactivePart.url else { + + return nil + } + + return InteractiveRange(range: range, url: url) + } + .forEach { range, url in + var partLinkAttributes = interactiveAttributes + partLinkAttributes.updateValue(url, forKey: .link) + + mutableAttributedText.addAttributes(partLinkAttributes, range: NSRange(range, in: text)) + } + + attributedText = mutableAttributedText + } +} diff --git a/TIUIElements/Sources/Views/URLInteractiveTextView/UITextViewURLInteractionHandler.swift b/TIUIElements/Sources/Views/URLInteractiveTextView/UITextViewURLInteractionHandler.swift new file mode 100644 index 00000000..6108d0e0 --- /dev/null +++ b/TIUIElements/Sources/Views/URLInteractiveTextView/UITextViewURLInteractionHandler.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UITextView +import TISwiftUtils + +public protocol UITextViewURLInteractionHandler: UITextViewDelegate { + var urlInteractionHandler: ParameterClosure? { get set } +} diff --git a/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextView.swift b/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextView.swift new file mode 100644 index 00000000..83fb79de --- /dev/null +++ b/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextView.swift @@ -0,0 +1,52 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UIGeometry +import TIUIKitCore + +open class URLInteractiveTextView: BaseInitializableTextView, ConfigurableView { + public var interactionHandler: UITextViewURLInteractionHandler? { + didSet { + delegate = interactionHandler + } + } + + open override func configureAppearance() { + super.configureAppearance() + + isEditable = false + + textContainerInset = UIEdgeInsets(top: .zero, + left: -textContainer.lineFragmentPadding, + bottom: .zero, + right: -textContainer.lineFragmentPadding) + } + + public func configure(with viewModel: URLInteractiveTextViewModel) { + set(text: viewModel.text, + primaryTextStyle: viewModel.primaryTextStyle, + interactiveParts: viewModel.interactiveParts, + interactivePartsStyle: viewModel.interactivePartsStyle) + + interactionHandler = viewModel.interactionHandler + } +} diff --git a/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextViewModel.swift b/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextViewModel.swift new file mode 100644 index 00000000..5ddad386 --- /dev/null +++ b/TIUIElements/Sources/Views/URLInteractiveTextView/URLInteractiveTextViewModel.swift @@ -0,0 +1,59 @@ +// +// Copyright (c) 2022 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TISwiftUtils +import TIUIKitCore +import UIKit.UITextView + +open class URLInteractiveTextViewModel { + public var text: String + public var primaryTextStyle: BaseTextAttributes + public var interactiveParts: [UITextView.InteractivePart] + public var interactivePartsStyle: BaseTextAttributes + public var interactionHandler: UITextViewURLInteractionHandler? + + public init(text: String, + primaryTextStyle: BaseTextAttributes, + interactiveParts: [UITextView.InteractivePart], + interactivePartsStyle: BaseTextAttributes, + interactionHandler: UITextViewURLInteractionHandler?) { + + self.text = text + self.primaryTextStyle = primaryTextStyle + self.interactiveParts = interactiveParts + self.interactivePartsStyle = interactivePartsStyle + self.interactionHandler = interactionHandler + } + + convenience init(text: String, + primaryTextStyle: BaseTextAttributes, + interactiveParts: [UITextView.InteractivePart], + interactivePartsStyle: BaseTextAttributes, + urlInteractionHandler: ParameterClosure?) { + + self.init(text: text, + primaryTextStyle: primaryTextStyle, + interactiveParts: interactiveParts, + interactivePartsStyle: interactivePartsStyle, + interactionHandler: DefaultUITextViewURLInteractionHandler(urlInteractionHandler: urlInteractionHandler)) + } +} diff --git a/TIUIKitCore/Sources/InitializableView/InitializeableViewController.swift b/TIUIKitCore/Sources/InitializableView/InitializableViewController.swift similarity index 92% rename from TIUIKitCore/Sources/InitializableView/InitializeableViewController.swift rename to TIUIKitCore/Sources/InitializableView/InitializableViewController.swift index 1f22f07b..e4f7bb5c 100644 --- a/TIUIKitCore/Sources/InitializableView/InitializeableViewController.swift +++ b/TIUIKitCore/Sources/InitializableView/InitializableViewController.swift @@ -20,12 +20,12 @@ // THE SOFTWARE. // -public protocol InitializeableViewController: InitializableViewProtocol { +public protocol InitializableViewController: InitializableViewProtocol { func configureBarButtons() } -public extension InitializeableViewController { +public extension InitializableViewController { func initializeView() { assertionFailure("Use \(String(describing: initializeController)) for UIViewController instead!") } diff --git a/TIUIKitCore/Sources/ViewControllers/BaseCustomViewController.swift b/TIUIKitCore/Sources/ViewControllers/BaseCustomViewController.swift index 927352cf..004f9032 100644 --- a/TIUIKitCore/Sources/ViewControllers/BaseCustomViewController.swift +++ b/TIUIKitCore/Sources/ViewControllers/BaseCustomViewController.swift @@ -22,7 +22,7 @@ import UIKit -open class BaseCustomViewController: BaseInitializeableViewController { +open class BaseCustomViewController: BaseInitializableViewController { public private(set) lazy var customView = createView() diff --git a/TIUIKitCore/Sources/ViewControllers/BaseInitializeableViewController.swift b/TIUIKitCore/Sources/ViewControllers/BaseInitializableViewController.swift similarity index 92% rename from TIUIKitCore/Sources/ViewControllers/BaseInitializeableViewController.swift rename to TIUIKitCore/Sources/ViewControllers/BaseInitializableViewController.swift index 48b4a7b6..75b98be0 100644 --- a/TIUIKitCore/Sources/ViewControllers/BaseInitializeableViewController.swift +++ b/TIUIKitCore/Sources/ViewControllers/BaseInitializableViewController.swift @@ -22,7 +22,7 @@ import UIKit.UIViewController -open class BaseInitializeableViewController: UIViewController, InitializeableViewController { +open class BaseInitializableViewController: UIViewController, InitializableViewController { override open func viewDidLoad() { super.viewDidLoad() @@ -34,7 +34,7 @@ open class BaseInitializeableViewController: UIViewController, InitializeableVie initializeController() } - // MARK: - InitializeableController + // MARK: - InitializableController open func addViews() { // override in subclass