From 5615148f95983ea13ade410e5929051dc6494f33 Mon Sep 17 00:00:00 2001 From: Konstantin Porokhov Date: Tue, 24 Jan 2023 17:40:02 +0400 Subject: [PATCH 1/4] added actionable label --- Utils.xcodeproj/project.pbxproj | 61 ++++ .../xcshareddata/swiftpm/Package.resolved | 9 + .../AccessibilityIdentifiers.swift | 15 + Utils/ActionableLabel/ActionableLabel.swift | 261 ++++++++++++++++++ .../ActionableLabelDefaultParameters.swift | 36 +++ .../Protocols/ViewAccessibilityProtocol.swift | 23 ++ Utils/String/StringAttribute+Array.swift | 20 ++ 7 files changed, 425 insertions(+) create mode 100644 Utils/ActionableLabel/Accessibility/AccessibilityIdentifiers.swift create mode 100644 Utils/ActionableLabel/ActionableLabel.swift create mode 100644 Utils/ActionableLabel/Protocols/ActionableLabelDefaultParameters.swift create mode 100644 Utils/ActionableLabel/Protocols/ViewAccessibilityProtocol.swift create mode 100644 Utils/String/StringAttribute+Array.swift diff --git a/Utils.xcodeproj/project.pbxproj b/Utils.xcodeproj/project.pbxproj index 7f86e9c..9c7a1ad 100644 --- a/Utils.xcodeproj/project.pbxproj +++ b/Utils.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 08E6092D2980000500C224CC /* Autolocalizable in Frameworks */ = {isa = PBXBuildFile; productRef = 08E6092C2980000500C224CC /* Autolocalizable */; }; + 08E609302980038800C224CC /* ActionableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6092F2980038800C224CC /* ActionableLabel.swift */; }; + 08E609322980066700C224CC /* StringAttribute+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E609312980066700C224CC /* StringAttribute+Array.swift */; }; + 08E6093729800E3A00C224CC /* ViewAccessibilityProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6093629800E3A00C224CC /* ViewAccessibilityProtocol.swift */; }; + 08E609392980101E00C224CC /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E609382980101E00C224CC /* AccessibilityIdentifiers.swift */; }; + 08E6093C298013D800C224CC /* ActionableLabelDefaultParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6093B298013D800C224CC /* ActionableLabelDefaultParameters.swift */; }; 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2361321D2150200169AC9 /* Dictionary+QueryStringBuilder.swift */; }; 3946574E24EC1A580069BDB0 /* LoadingViewBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3946574D24EC1A580069BDB0 /* LoadingViewBlock.swift */; }; 3946575024EC1AAC0069BDB0 /* LoadingViewConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3946574F24EC1AAC0069BDB0 /* LoadingViewConfig.swift */; }; @@ -110,6 +116,11 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 08E6092F2980038800C224CC /* ActionableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabel.swift; sourceTree = ""; }; + 08E609312980066700C224CC /* StringAttribute+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StringAttribute+Array.swift"; sourceTree = ""; }; + 08E6093629800E3A00C224CC /* ViewAccessibilityProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewAccessibilityProtocol.swift; sourceTree = ""; }; + 08E609382980101E00C224CC /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; + 08E6093B298013D800C224CC /* ActionableLabelDefaultParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelDefaultParameters.swift; sourceTree = ""; }; 18F2361321D2150200169AC9 /* Dictionary+QueryStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+QueryStringBuilder.swift"; sourceTree = ""; }; 39262D442551713B00591787 /* PinCryptoBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinCryptoBoxTests.swift; sourceTree = ""; }; 39262D4C2551714F00591787 /* PinHackCryptoBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinHackCryptoBoxTests.swift; sourceTree = ""; }; @@ -212,6 +223,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 08E6092D2980000500C224CC /* Autolocalizable in Frameworks */, 90AAE2FE2851F2890088A5A4 /* CryptoSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -227,6 +239,33 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 08E6092E2980035500C224CC /* ActionableLabel */ = { + isa = PBXGroup; + children = ( + 08E6093A2980138100C224CC /* Protocols */, + 08E6093329800DEA00C224CC /* Accessibility */, + 08E6092F2980038800C224CC /* ActionableLabel.swift */, + ); + path = ActionableLabel; + sourceTree = ""; + }; + 08E6093329800DEA00C224CC /* Accessibility */ = { + isa = PBXGroup; + children = ( + 08E609382980101E00C224CC /* AccessibilityIdentifiers.swift */, + ); + path = Accessibility; + sourceTree = ""; + }; + 08E6093A2980138100C224CC /* Protocols */ = { + isa = PBXGroup; + children = ( + 08E6093629800E3A00C224CC /* ViewAccessibilityProtocol.swift */, + 08E6093B298013D800C224CC /* ActionableLabelDefaultParameters.swift */, + ); + path = Protocols; + sourceTree = ""; + }; 18F2361221D214CC00169AC9 /* Dictionary */ = { isa = PBXGroup; children = ( @@ -377,6 +416,7 @@ 4F3ED9E1211C27CF0030DD45 /* Utils */ = { isa = PBXGroup; children = ( + 08E6092E2980035500C224CC /* ActionableLabel */, A4F4B13524ED2490009FA920 /* BeanPageControl */, 80437D24214045B30095A8D0 /* BrightSide */, 5709EC5B236F4C2200EEBD93 /* CommonButton */, @@ -429,6 +469,7 @@ children = ( 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */, D6040DFB22C5F3E20088BB50 /* StringBuilder.swift */, + 08E609312980066700C224CC /* StringAttribute+Array.swift */, ); path = String; sourceTree = ""; @@ -743,6 +784,7 @@ name = Utils; packageProductDependencies = ( 90AAE2FD2851F2890088A5A4 /* CryptoSwift */, + 08E6092C2980000500C224CC /* Autolocalizable */, ); productName = Utils; productReference = 4F3ED9DF211C27CF0030DD45 /* Utils.framework */; @@ -796,6 +838,7 @@ mainGroup = 4F3ED9D5211C27CF0030DD45; packageReferences = ( 90AAE2FC2851F2890088A5A4 /* XCRemoteSwiftPackageReference "CryptoSwift" */, + 08E6092B2980000500C224CC /* XCRemoteSwiftPackageReference "Autolocalizable" */, ); productRefGroup = 4F3ED9E0211C27CF0030DD45 /* Products */; projectDirPath = ""; @@ -862,6 +905,7 @@ C11CEB4824D44E2C00C1CD0F /* MailSenderRouterHelper.swift in Sources */, 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */, 89293B2621F59F6A0016C6BE /* LocalStorage.swift in Sources */, + 08E6093729800E3A00C224CC /* ViewAccessibilityProtocol.swift in Sources */, 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */, E9B063102147AECC0080C391 /* UIDevice+feedbackType.swift in Sources */, 9087BC5421EF3BF200FCE1E1 /* FullKeyboardPresentable.swift in Sources */, @@ -877,8 +921,10 @@ A4D5D5FD24E57A6A004ABFBC /* CustomSwitch.swift in Sources */, 3946575524EC1E600069BDB0 /* BaseLoadingView.swift in Sources */, 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */, + 08E609392980101E00C224CC /* AccessibilityIdentifiers.swift in Sources */, 3946575924EC21350069BDB0 /* LoadingSubview.swift in Sources */, 9E7724CA2562C3CC00E86D81 /* MailSenderPayload.swift in Sources */, + 08E6093C298013D800C224CC /* ActionableLabelDefaultParameters.swift in Sources */, E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */, 3971F72524F1843900597F9D /* SecureStore.swift in Sources */, 3971F72324F1843900597F9D /* InMemorySecureStore.swift in Sources */, @@ -890,6 +936,7 @@ 80437D26214045EF0095A8D0 /* BrightSide.swift in Sources */, A4F4B13724ED24A4009FA920 /* BeanPageControl.swift in Sources */, 898845202360482D004940DC /* UIView+XibSetup.swift in Sources */, + 08E609322980066700C224CC /* StringAttribute+Array.swift in Sources */, 90AC8567238592A700DF7F3B /* GeolocationAccuracy.swift in Sources */, 5709EC5D236F4C6500EEBD93 /* CommonButton.swift in Sources */, 579EAFCE27022F47004E092B /* GeolocationServiceInterface.swift in Sources */, @@ -907,6 +954,7 @@ 90718AF121EA370000C81002 /* KeyboardNotificationsObserver.swift in Sources */, 397CDAD124EF9F6C00FB0EAA /* PinCryptoBox.swift in Sources */, 39DCF1A224EE7C66007CCFBC /* UIImage+badgedImage.swift in Sources */, + 08E609302980038800C224CC /* ActionableLabel.swift in Sources */, 904DE28B272DCF70004FDC64 /* MapRoutingLocationServiceInterface.swift in Sources */, 90AC8569238592AF00DF7F3B /* GeolocationAuthResult.swift in Sources */, 5709EC6A236F562400EEBD93 /* UIImageExtensions.swift in Sources */, @@ -1215,6 +1263,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 08E6092B2980000500C224CC /* XCRemoteSwiftPackageReference "Autolocalizable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/surfstudio/Autolocalizable.git"; + requirement = { + kind = exactVersion; + version = 1.1.0; + }; + }; 90AAE2FC2851F2890088A5A4 /* XCRemoteSwiftPackageReference "CryptoSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift"; @@ -1226,6 +1282,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 08E6092C2980000500C224CC /* Autolocalizable */ = { + isa = XCSwiftPackageProductDependency; + package = 08E6092B2980000500C224CC /* XCRemoteSwiftPackageReference "Autolocalizable" */; + productName = Autolocalizable; + }; 90AAE2FD2851F2890088A5A4 /* CryptoSwift */ = { isa = XCSwiftPackageProductDependency; package = 90AAE2FC2851F2890088A5A4 /* XCRemoteSwiftPackageReference "CryptoSwift" */; diff --git a/Utils.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Utils.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 183cb74..b052b69 100644 --- a/Utils.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Utils.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "autolocalizable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/surfstudio/Autolocalizable.git", + "state" : { + "revision" : "a78a12a7b2d8dab688365f0ec7504d0bbaf838f1", + "version" : "1.1.0" + } + }, { "identity" : "cryptoswift", "kind" : "remoteSourceControl", diff --git a/Utils/ActionableLabel/Accessibility/AccessibilityIdentifiers.swift b/Utils/ActionableLabel/Accessibility/AccessibilityIdentifiers.swift new file mode 100644 index 0000000..c3b6146 --- /dev/null +++ b/Utils/ActionableLabel/Accessibility/AccessibilityIdentifiers.swift @@ -0,0 +1,15 @@ +// +// AccessibilityIdentifiers.swift +// Unicredit +// +// Created by Егор Егоров on 12/02/2020. +// Copyright © 2020 Surf. All rights reserved. +// + +public enum AccessibilityIdentifiers { + + public enum Label { + public static let actionable = "actionable_label" + } + +} diff --git a/Utils/ActionableLabel/ActionableLabel.swift b/Utils/ActionableLabel/ActionableLabel.swift new file mode 100644 index 0000000..bc51be0 --- /dev/null +++ b/Utils/ActionableLabel/ActionableLabel.swift @@ -0,0 +1,261 @@ +// +// ActionableLabel.swift +// Unicredit +// +// Created by Alexander Filimonov on 30/07/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import Autolocalizable +import UIKit + +public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, ActionableLabelParameters { + + // MARK: - Nested types + + public typealias TextAction = () -> Void + + public typealias Part = ( + text: LocalizableStringItem, + attributes: [StringAttribute], + isHighlighted: Bool, + action: TextAction? + ) + + // MARK: - Public properties + + public lazy var globalAttributes: [StringAttribute] = defaultAttributes { + didSet { + builder = StringBuilder(globalAttributes: globalAttributes) + reload() + } + } + + // MARK: - Private properties + + private var parts: [Part] = [] + + private var heightCorrection: CGFloat = 0 + + private lazy var textStorage = NSTextStorage() + private lazy var layoutManager = NSLayoutManager() + private lazy var textContainer = NSTextContainer() + + private lazy var builder = StringBuilder(globalAttributes: globalAttributes) + + // MARK: - UILabel properties + + public override var numberOfLines: Int { + didSet { textContainer.maximumNumberOfLines = numberOfLines } + } + + public override var lineBreakMode: NSLineBreakMode { + didSet { textContainer.lineBreakMode = lineBreakMode } + } + + // MARK: - Initialization and deinitialization + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + // MARK: - UILabel properties + + public override func drawText(in rect: CGRect) { + let range = NSRange(location: 0, length: textStorage.length) + + textContainer.size = rect.size + let newOrigin = textOrigin(inRect: rect) + + layoutManager.drawBackground(forGlyphRange: range, at: newOrigin) + layoutManager.drawGlyphs(forGlyphRange: range, at: newOrigin) + } + + // MARK: - Auto layout + + public override var intrinsicContentSize: CGSize { + let superSize = super.intrinsicContentSize + textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) + let size = layoutManager.usedRect(for: textContainer) + return CGSize(width: ceil(size.width), height: ceil(size.height + heightCorrection)) + } + + // MARK: - Public methods + + public func clear() { + parts.removeAll() + text = nil + attributedText = nil + } + + public func append(text: LocalizableStringItem, attributes: [StringAttribute] = [], action: TextAction? = nil) { + parts.append((text: text, attributes: attributes, isHighlighted: false, action: action)) + reload() + } + + public func setHighlighted(at index: Int) { + parts[index].isHighlighted = true + reload() + } + + // MARK: - Private methods + + private func commonInit() { + setAccessibilityIdentifier(AccessibilityIdentifiers.Label.actionable, for: self) + textStorage.addLayoutManager(layoutManager) + layoutManager.addTextContainer(textContainer) + textContainer.lineFragmentPadding = 0 + textContainer.lineBreakMode = lineBreakMode + isUserInteractionEnabled = true + + // Localization + registration(key: localizableKey, item: LocalizableStringItem()) { [weak self] _, _ in + self?.reload() + self?.layoutIfNeeded() + } + } + + private func textOrigin(inRect rect: CGRect) -> CGPoint { + let usedRect = layoutManager.usedRect(for: textContainer) + heightCorrection = (rect.height - usedRect.height) / 2 + let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y + return CGPoint(x: rect.origin.x, y: glyphOriginY) + } + + private func unhighlightAllParts() { + parts.enumerated().forEach { curIndex, _ in + parts[curIndex].isHighlighted = false + } + reload() + } + + private func reload() { + builder.clear() + + for part in parts { + var currenAttributes: [StringAttribute] = part.attributes + if let foregroundColor = part.attributes.foregroundColor { + let color = part.isHighlighted + ? foregroundColor.withAlphaComponent(highlightedAlpha) + : foregroundColor + currenAttributes.append(.foregroundColor(color)) + } + builder.add(.string(part.text.value), with: currenAttributes) + } + let attributedString = builder.value + + attributedText = attributedString + textStorage.setAttributedString(attributedString) + + setNeedsDisplay() + } + + /// Method for detecting is part tapped + /// + /// - Parameters: + /// - part: some part of label + /// - location: location of tap + /// - Returns: is this part located on passed location + private func isPart(_ part: Part, locatedAt location: CGPoint) -> Bool { + guard textStorage.length > 0, let text = self.text else { + return false + } + + var correctLocation = location + correctLocation.y -= heightCorrection + let boundingRect = layoutManager.boundingRect(forGlyphRange: NSRange(location: 0, length: textStorage.length), + in: textContainer) + guard boundingRect.contains(correctLocation) else { + return false + } + + let index = layoutManager.glyphIndex(for: correctLocation, in: textContainer) + + let range = (text as NSString).range(of: part.text.value) + return index >= range.location && index <= range.location + range.length + } + +} + +// MARK: - Handle UI Responder touches + +extension ActionableLabel { + + /// Method for handling touch + /// + /// - Parameter touch: UITouch object + /// - Returns: should we avoid super method call + private func onTouch(_ touch: UITouch) -> Bool { + let location = touch.location(in: self) + + let filteredParts = parts.enumerated().filter { + return $0.element.action != nil + && isPart($0.element, locatedAt: location) + && bounds.contains(location) + } + + switch touch.phase { + case .began: + filteredParts.forEach { + setHighlighted(at: $0.offset) + } + case .ended: + unhighlightAllParts() + filteredParts.forEach { + $0.element.action?() + } + case .cancelled: + unhighlightAllParts() + return false + default: + return false + } + + return !filteredParts.isEmpty + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { + return + } + if onTouch(touch) { + return + } + super.touchesBegan(touches, with: event) + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { + return + } + if onTouch(touch) { + return + } + super.touchesMoved(touches, with: event) + } + + public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { + return + } + _ = onTouch(touch) + super.touchesCancelled(touches, with: event) + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { + return + } + if onTouch(touch) { + return + } + super.touchesEnded(touches, with: event) + } + +} diff --git a/Utils/ActionableLabel/Protocols/ActionableLabelDefaultParameters.swift b/Utils/ActionableLabel/Protocols/ActionableLabelDefaultParameters.swift new file mode 100644 index 0000000..e8b22f3 --- /dev/null +++ b/Utils/ActionableLabel/Protocols/ActionableLabelDefaultParameters.swift @@ -0,0 +1,36 @@ +// +// ActionableLabelDefaultParameters.swift +// Unicredit +// +// Created by Егор Егоров on 12/02/2020. +// Copyright © 2020 Surf. All rights reserved. +// + +import UIKit + +public protocol ActionableLabelParameters { + var highlightedAlpha: CGFloat { get } + var localizableKey: String { get } + var defaultAttributes: [StringAttribute] { get } +} + +public extension ActionableLabelParameters { + + var highlightedAlpha: CGFloat { + return 0.5 + } + + var localizableKey: String { + return "actionableLabel" + } + + var defaultAttributes: [StringAttribute] { + return [ + .font(.systemFont(ofSize: 14)), + .aligment(.left), + .lineSpacing(.zero), + .foregroundColor(.black) + ] + } + +} diff --git a/Utils/ActionableLabel/Protocols/ViewAccessibilityProtocol.swift b/Utils/ActionableLabel/Protocols/ViewAccessibilityProtocol.swift new file mode 100644 index 0000000..d400d7b --- /dev/null +++ b/Utils/ActionableLabel/Protocols/ViewAccessibilityProtocol.swift @@ -0,0 +1,23 @@ +// +// ViewAccessibilityProtocol.swift +// Unicredit +// +// Created by Егор Егоров on 12/02/2020. +// Copyright © 2020 Surf. All rights reserved. +// + +import UIKit + +public protocol ViewAccessibilityProtocol { + + func setAccessibilityIdentifier(_ identifier: String, for item: UIAccessibilityIdentification) + +} + +public extension ViewAccessibilityProtocol { + + func setAccessibilityIdentifier(_ identifier: String, for item: UIAccessibilityIdentification) { + item.accessibilityIdentifier = identifier + } + +} diff --git a/Utils/String/StringAttribute+Array.swift b/Utils/String/StringAttribute+Array.swift new file mode 100644 index 0000000..f98c1d8 --- /dev/null +++ b/Utils/String/StringAttribute+Array.swift @@ -0,0 +1,20 @@ +// +// StringAttribute+Array.swift +// Utils +// +// Created by Konstantin Porokhov on 24.01.2023. +// Copyright © 2023 Surf. All rights reserved. +// + +import UIKit + +public extension Array where Element == StringAttribute { + var foregroundColor: UIColor? { + for attribute in self { + if case .foregroundColor(let foregroundColor) = attribute { + return foregroundColor + } + } + return nil + } +} From 7220870195bcb7d7e646ca8112dcfe1e3b860225 Mon Sep 17 00:00:00 2001 From: Konstantin Porokhov Date: Tue, 24 Jan 2023 18:46:48 +0400 Subject: [PATCH 2/4] added label in example --- Package.swift | 7 +- .../UtilsExample.xcodeproj/project.pbxproj | 68 ++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 +++ UtilsExample/UtilsExample/AppDelegate.swift | 1 + .../ActionableLabelCoordinator.swift | 33 +++++++++ .../ActionableLabelModuleConfigurator.swift | 23 ++++++ .../ActionableLabelModuleInput.swift | 10 +++ .../ActionableLabelModuleOutput.swift | 10 +++ .../Presenter/ActionableLabelPresenter.swift | 41 +++++++++++ .../View/ActionableLabelViewController.swift | 72 +++++++++++++++++++ .../View/ActionableLabelViewController.xib | 44 ++++++++++++ .../View/ActionableLabelViewInput.swift | 13 ++++ .../View/ActionableLabelViewOutput.swift | 12 ++++ 13 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 UtilsExample/UtilsExample/Playbook/Coordinators/ActionableLabelCoordinator.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Configurator/ActionableLabelModuleConfigurator.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleInput.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleOutput.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.xib create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewInput.swift create mode 100644 UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewOutput.swift diff --git a/Package.swift b/Package.swift index 63bb9e9..63e6cb1 100644 --- a/Package.swift +++ b/Package.swift @@ -16,12 +16,17 @@ let package = Package( name: "CryptoSwift", url: "https://github.com/krzyzanowskim/CryptoSwift", .exact("1.5.1") + ), + .package( + name: "Autolocalizable", + url: "https://github.com/surfstudio/Autolocalizable.git", + from: "1.1.0" ) ], targets: [ .target( name: "Utils", - dependencies: ["CryptoSwift"], + dependencies: ["CryptoSwift", "Autolocalizable"], path: "Utils", exclude: [ "Info.plist" diff --git a/UtilsExample/UtilsExample.xcodeproj/project.pbxproj b/UtilsExample/UtilsExample.xcodeproj/project.pbxproj index 58e03fc..f4e3232 100644 --- a/UtilsExample/UtilsExample.xcodeproj/project.pbxproj +++ b/UtilsExample/UtilsExample.xcodeproj/project.pbxproj @@ -7,6 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 08E6094329801EC900C224CC /* ActionableLabelModuleConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094229801EC900C224CC /* ActionableLabelModuleConfigurator.swift */; }; + 08E6094829801F0900C224CC /* ActionableLabelModuleOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094529801F0900C224CC /* ActionableLabelModuleOutput.swift */; }; + 08E6094929801F0900C224CC /* ActionableLabelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094629801F0900C224CC /* ActionableLabelPresenter.swift */; }; + 08E6094A29801F0900C224CC /* ActionableLabelModuleInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094729801F0900C224CC /* ActionableLabelModuleInput.swift */; }; + 08E6094F29801F1600C224CC /* ActionableLabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094B29801F1600C224CC /* ActionableLabelViewController.swift */; }; + 08E6095029801F1600C224CC /* ActionableLabelViewInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094C29801F1600C224CC /* ActionableLabelViewInput.swift */; }; + 08E6095129801F1600C224CC /* ActionableLabelViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08E6094D29801F1600C224CC /* ActionableLabelViewController.xib */; }; + 08E6095229801F1600C224CC /* ActionableLabelViewOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6094E29801F1600C224CC /* ActionableLabelViewOutput.swift */; }; + 08E609542980217400C224CC /* ActionableLabelCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E609532980217400C224CC /* ActionableLabelCoordinator.swift */; }; 90AAE30C2851F6100088A5A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90AAE30B2851F6100088A5A4 /* AppDelegate.swift */; }; 90AAE3152851F6110088A5A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90AAE3142851F6110088A5A4 /* Assets.xcassets */; }; 90AAE3182851F6110088A5A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90AAE3162851F6110088A5A4 /* LaunchScreen.storyboard */; }; @@ -148,6 +157,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 08E6094229801EC900C224CC /* ActionableLabelModuleConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelModuleConfigurator.swift; sourceTree = ""; }; + 08E6094529801F0900C224CC /* ActionableLabelModuleOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelModuleOutput.swift; sourceTree = ""; }; + 08E6094629801F0900C224CC /* ActionableLabelPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelPresenter.swift; sourceTree = ""; }; + 08E6094729801F0900C224CC /* ActionableLabelModuleInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelModuleInput.swift; sourceTree = ""; }; + 08E6094B29801F1600C224CC /* ActionableLabelViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelViewController.swift; sourceTree = ""; }; + 08E6094C29801F1600C224CC /* ActionableLabelViewInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelViewInput.swift; sourceTree = ""; }; + 08E6094D29801F1600C224CC /* ActionableLabelViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ActionableLabelViewController.xib; sourceTree = ""; }; + 08E6094E29801F1600C224CC /* ActionableLabelViewOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelViewOutput.swift; sourceTree = ""; }; + 08E609532980217400C224CC /* ActionableLabelCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionableLabelCoordinator.swift; sourceTree = ""; }; 90719DA52851F8FC00B42F99 /* iOS-Utils */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "iOS-Utils"; path = ..; sourceTree = ""; }; 90AAE3082851F6100088A5A4 /* UtilsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UtilsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 90AAE30B2851F6100088A5A4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -301,6 +319,45 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 08E6093E29801D5800C224CC /* ActionableLabel */ = { + isa = PBXGroup; + children = ( + 08E6093F29801D6400C224CC /* Configurator */, + 08E6094429801F0900C224CC /* Presenter */, + 08E6094129801E4D00C224CC /* View */, + ); + path = ActionableLabel; + sourceTree = ""; + }; + 08E6093F29801D6400C224CC /* Configurator */ = { + isa = PBXGroup; + children = ( + 08E6094229801EC900C224CC /* ActionableLabelModuleConfigurator.swift */, + ); + path = Configurator; + sourceTree = ""; + }; + 08E6094129801E4D00C224CC /* View */ = { + isa = PBXGroup; + children = ( + 08E6094B29801F1600C224CC /* ActionableLabelViewController.swift */, + 08E6094D29801F1600C224CC /* ActionableLabelViewController.xib */, + 08E6094C29801F1600C224CC /* ActionableLabelViewInput.swift */, + 08E6094E29801F1600C224CC /* ActionableLabelViewOutput.swift */, + ); + path = View; + sourceTree = ""; + }; + 08E6094429801F0900C224CC /* Presenter */ = { + isa = PBXGroup; + children = ( + 08E6094529801F0900C224CC /* ActionableLabelModuleOutput.swift */, + 08E6094629801F0900C224CC /* ActionableLabelPresenter.swift */, + 08E6094729801F0900C224CC /* ActionableLabelModuleInput.swift */, + ); + path = Presenter; + sourceTree = ""; + }; 90719DA42851F8FC00B42F99 /* Packages */ = { isa = PBXGroup; children = ( @@ -389,6 +446,7 @@ A4435F372864E5D500A4207E /* Flows */ = { isa = PBXGroup; children = ( + 08E6093E29801D5800C224CC /* ActionableLabel */, A459CAEC2865E0C100AF463F /* BrightSide */, A459CA6D2864EA6500AF463F /* CustomSwitch */, A459CAD62865D44D00AF463F /* GeolocationService */, @@ -447,6 +505,7 @@ A459CA812864EB5A00AF463F /* Coordinators */ = { isa = PBXGroup; children = ( + 08E609532980217400C224CC /* ActionableLabelCoordinator.swift */, A459CB002865E15400AF463F /* BrightSideCoordinator.swift */, A459CA822864EB8000AF463F /* CustomSwitchCoordinator.swift */, A459CAEA2865DAC800AF463F /* GeolocationServiceCoordinator.swift */, @@ -1041,6 +1100,7 @@ 90AAE3182851F6110088A5A4 /* LaunchScreen.storyboard in Resources */, A459CAFC2865E0C100AF463F /* BrightSideViewController.xib in Resources */, 90AAE3152851F6110088A5A4 /* Assets.xcassets in Resources */, + 08E6095129801F1600C224CC /* ActionableLabelViewController.xib in Resources */, A459CB142865E1A300AF463F /* RouteMeasurerViewController.xib in Resources */, A459CA7F2864EA6500AF463F /* CustomSwitchViewController.xib in Resources */, A459CAA22864F10400AF463F /* StringAttributesViewController.xib in Resources */, @@ -1108,12 +1168,15 @@ A459CA9F2864F10400AF463F /* StringAttributesModuleInput.swift in Sources */, A459CB94286603FC00AF463F /* MoneyModelModuleInput.swift in Sources */, A459CA7D2864EA6500AF463F /* CustomSwitchViewController.swift in Sources */, + 08E6094329801EC900C224CC /* ActionableLabelModuleConfigurator.swift in Sources */, A459CAA32864F10400AF463F /* StringAttributesViewInput.swift in Sources */, + 08E6095029801F1600C224CC /* ActionableLabelViewInput.swift in Sources */, A459CAB42864F55E00AF463F /* QueryStringBuilderModuleInput.swift in Sources */, A459CAFB2865E0C100AF463F /* BrightSideViewInput.swift in Sources */, A459CA7B2864EA6500AF463F /* CustomSwitchModuleOutput.swift in Sources */, A459CB572865E6BF00AF463F /* KeyboardPresentableModuleConfigurator.swift in Sources */, A459CB692865E9B500AF463F /* SkeletonViewViewInput.swift in Sources */, + 08E6095229801F1600C224CC /* ActionableLabelViewOutput.swift in Sources */, A459CA7A2864EA6500AF463F /* CustomSwitchPresenter.swift in Sources */, A409293D286113E300BBC569 /* MainPage.swift in Sources */, A459CA8E2864ED9F00AF463F /* Presentable.swift in Sources */, @@ -1142,6 +1205,7 @@ A459CB0E2865E1A300AF463F /* RouteMeasurerModuleInput.swift in Sources */, A459CB412865E44900AF463F /* WordDeclinationSelectorModuleConfigurator.swift in Sources */, A459CB532865E6BF00AF463F /* KeyboardPresentableViewController.swift in Sources */, + 08E6094A29801F0900C224CC /* ActionableLabelModuleInput.swift in Sources */, A459CAF82865E0C100AF463F /* BrightSideModuleOutput.swift in Sources */, A459CB2D2865E3F300AF463F /* SettingsRouterCoordinator.swift in Sources */, A459CAA02864F10400AF463F /* StringAttributesViewOutput.swift in Sources */, @@ -1172,6 +1236,8 @@ A459CA792864EA6500AF463F /* CustomSwitchModuleInput.swift in Sources */, A459CB432865E4DE00AF463F /* WordDeclinationSelectorCoordinator.swift in Sources */, A459CB252865E31900AF463F /* SettingsRouterModuleOutput.swift in Sources */, + 08E6094829801F0900C224CC /* ActionableLabelModuleOutput.swift in Sources */, + 08E6094F29801F1600C224CC /* ActionableLabelViewController.swift in Sources */, A459CB6C2865E9B500AF463F /* SkeletonViewViewOutput.swift in Sources */, A459CA9D2864F10400AF463F /* StringAttributesModuleOutput.swift in Sources */, A459CAE32865D44D00AF463F /* GeolocationServicePresenter.swift in Sources */, @@ -1186,8 +1252,10 @@ A459CB292865E31900AF463F /* SettingsRouterViewOutput.swift in Sources */, A459CB3C2865E44900AF463F /* WordDeclinationSelectorModuleInput.swift in Sources */, A459CAD32864FD1200AF463F /* UIDeviceModuleConfigurator.swift in Sources */, + 08E609542980217400C224CC /* ActionableLabelCoordinator.swift in Sources */, A459CB112865E1A300AF463F /* RouteMeasurerViewInput.swift in Sources */, A459CB98286603FC00AF463F /* MoneyModelViewOutput.swift in Sources */, + 08E6094929801F0900C224CC /* ActionableLabelPresenter.swift in Sources */, A459CAE62865D44D00AF463F /* GeolocationServiceViewInput.swift in Sources */, A459CAF92865E0C100AF463F /* BrightSidePresenter.swift in Sources */, A459CB682865E9B500AF463F /* SkeletonViewModuleInput.swift in Sources */, diff --git a/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ce9102b..912c28b 100644 --- a/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "Autolocalizable", + "repositoryURL": "https://github.com/surfstudio/Autolocalizable.git", + "state": { + "branch": null, + "revision": "a78a12a7b2d8dab688365f0ec7504d0bbaf838f1", + "version": "1.1.0" + } + }, { "package": "CryptoSwift", "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift", diff --git a/UtilsExample/UtilsExample/AppDelegate.swift b/UtilsExample/UtilsExample/AppDelegate.swift index bfe1ab9..f47d583 100644 --- a/UtilsExample/UtilsExample/AppDelegate.swift +++ b/UtilsExample/UtilsExample/AppDelegate.swift @@ -24,6 +24,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { window?.makeKeyAndVisible() Playbook.shared + .add(flowCoordinator: ActionableLabelCoordinator()) .add(flowCoordinator: BrightSideCoordinator()) .add(flowCoordinator: CustomSwitchCoordinator()) .add(flowCoordinator: GeolocationServiceCoordinator()) diff --git a/UtilsExample/UtilsExample/Playbook/Coordinators/ActionableLabelCoordinator.swift b/UtilsExample/UtilsExample/Playbook/Coordinators/ActionableLabelCoordinator.swift new file mode 100644 index 0000000..2fddad9 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Coordinators/ActionableLabelCoordinator.swift @@ -0,0 +1,33 @@ +// +// ActionableLabelCoordinator.swift +// UtilsExample +// +// Created by Евгений Васильев on 24.06.2022. +// + +import SurfPlaybook + +final class ActionableLabelCoordinator: PlaybookFlowCoordinator { + + // MARK: - Private Properties + + private let router = MainRouter() + + // MARK: - PlaybookFlowCoordinator + + var id: String { + return "ActionableLabelCoordinator" + } + + var name: String { + return "ActionableLabelCoordinator" + } + + var type: FlowCoordinatorType { + return .coordinator { [weak self] in + let (view, _) = ActionableLabelModuleConfigurator().configure() + self?.router.present(view) + } + } + +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Configurator/ActionableLabelModuleConfigurator.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Configurator/ActionableLabelModuleConfigurator.swift new file mode 100644 index 0000000..42bf199 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Configurator/ActionableLabelModuleConfigurator.swift @@ -0,0 +1,23 @@ +// +// ActionableLabelModuleConfigurator.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +import UIKit + +final class ActionableLabelModuleConfigurator { + + func configure() -> (UIViewController, ActionableLabelModuleOutput) { + let view = ActionableLabelViewController() + let presenter = ActionableLabelPresenter() + + presenter.view = view + view.output = presenter + + return (view, presenter) + } + +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleInput.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleInput.swift new file mode 100644 index 0000000..b818d4d --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleInput.swift @@ -0,0 +1,10 @@ +// +// ActionableLabelModuleInput.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +protocol ActionableLabelModuleInput: AnyObject { +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleOutput.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleOutput.swift new file mode 100644 index 0000000..61a8730 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelModuleOutput.swift @@ -0,0 +1,10 @@ +// +// ActionableLabelModuleOutput.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +protocol ActionableLabelModuleOutput: AnyObject { +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift new file mode 100644 index 0000000..3b49164 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift @@ -0,0 +1,41 @@ +// +// ActionableLabelPresenter.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +import Autolocalizable + +final class ActionableLabelPresenter: ActionableLabelModuleOutput { + + // MARK: - BrightSideModuleOutput + + // MARK: - Properties + + weak var view: ActionableLabelViewInput? + +} + +// MARK: - BrightSideModuleInput + +extension ActionableLabelPresenter: ActionableLabelModuleInput { +} + +// MARK: - BrightSideViewOutput + +extension ActionableLabelPresenter: ActionableLabelViewOutput { + + func viewLoaded() { + view?.setupInitialState() + view?.configure(with: [ + (text: LocalizableStringItem("Start sentences "), didSelect: nil), + (text: LocalizableStringItem("link text"), didSelect: { + print("link selected") + }), + (text: LocalizableStringItem(" end sentences."), didSelect: nil) + ]) + } + +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift new file mode 100644 index 0000000..de485e7 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift @@ -0,0 +1,72 @@ +// +// ActionableLabelViewController.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +import UIKit +import Utils +import Autolocalizable + +final class ActionableLabelViewController: UIViewController { + + // MARK: - Nested types + + public typealias Model = [(text: LocalizableStringItem, didSelect: (() -> Void)?)] + + // MARK: - IBOutlets + + @IBOutlet private weak var subTitle: ActionableLabel! + + // MARK: - Properties + + var output: ActionableLabelViewOutput? + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + output?.viewLoaded() + } + +} + +// MARK: - ActionableLabelViewInput + +extension ActionableLabelViewController: ActionableLabelViewInput { + + func setupInitialState() { + configureLabel() + } + + func configure(with model: Model) { + subTitle.clear() + model.forEach { part in + let isLink = part.didSelect != nil + subTitle.append( + text: part.text, + attributes: isLink ? [.font(.systemFont(ofSize: 20)), .foregroundColor(.link)] : [], + action: { + part.didSelect?() + } + ) + } + } + +} + +// MARK: - Private Methods + +private extension ActionableLabelViewController { + + func configureLabel() { + subTitle.globalAttributes = [ + .lineSpacing(0), + .foregroundColor(.black), + .font(.systemFont(ofSize: 14)) + ] + } + +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.xib b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.xib new file mode 100644 index 0000000..61bab80 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.xib @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewInput.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewInput.swift new file mode 100644 index 0000000..a04a3cb --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewInput.swift @@ -0,0 +1,13 @@ +// +// ActionableLabelViewInput.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +protocol ActionableLabelViewInput: AnyObject { + /// Method for setup initial state of view + func setupInitialState() + func configure(with model: ActionableLabelViewController.Model) +} diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewOutput.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewOutput.swift new file mode 100644 index 0000000..11e26f1 --- /dev/null +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewOutput.swift @@ -0,0 +1,12 @@ +// +// ActionableLabelViewOutput.swift +// UtilsExample +// +// Created by Evgeny Vasilev on 24/06/2022. +// Copyright © 2022 Surf. All rights reserved. +// + +protocol ActionableLabelViewOutput { + /// Notify presenter that view is ready + func viewLoaded() +} From c67c8b4132b675c5721bbfbd0c892229396e87a5 Mon Sep 17 00:00:00 2001 From: Konstantin Porokhov Date: Tue, 24 Jan 2023 18:57:40 +0400 Subject: [PATCH 3/4] added doc --- TechDocs/uikit_utils.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/TechDocs/uikit_utils.md b/TechDocs/uikit_utils.md index 1695984..9f10c0b 100644 --- a/TechDocs/uikit_utils.md +++ b/TechDocs/uikit_utils.md @@ -2,6 +2,7 @@ ## Содержание +- [ActionableLabel](#actionableLabel) - Лэйбл сбирающийся из строк с разными аттрибутами - [BlurBuilder](#blurbuilder) - упрощение работы с blur-эффектом - [ItemsScrollManager](#itemsscrollmanager) - менеджер для поэлементного скролла карусели - [KeyboardPresentable](#keyboardpresentable) - семейство протоколов для упрощения работы с клавиатурой и сокращения количества одинакового кода @@ -17,6 +18,41 @@ - [TouchableControl](#touchablecontrol) – аналог кнопки с кастомизированным анимированием - [CustomSwitch](#customswitch) – более гибкая реализация Switch ui элемента +### ActionableLabel + +Лэйбл который можно собирать из текстов с разными аттрибутами, так же обрабатывает нажатия, например ссылки. +LocalizableStringItem - принимает в себя простую строку. + +Пример: + +Во вью +```Swift +typealias Model = [(text: LocalizableStringItem, didSelect: (()-> Void)?)] + +func configure(with model: Model) { + label.clear() + model.forEach { part in + label.append( + text: part.text, + attributes: part.didSelect == nil ? [] : [.foregroundColor(selectColor)], + action: { + part.didSelect?() + } + ) + } +} +``` + +В презенторе +```Swift + +view?.configure(with: [ + (text: LocalizableStringItem("Start sentences "), didSelect: nil), + (text: LocalizableStringItem("link text"), didSelect: { print("link selected") }), + (text: LocalizableStringItem(" end sentences."), didSelect: nil) +]) +``` + ### BlurBuilder Утилита для упрощения добавления стандартного блюра на какое-либо View, позволяет управлять стилем и цветом блюра. @@ -495,4 +531,4 @@ customSwitch.setOn(true, animated: false) @IBAction func switchValueDidChange(_ sender: CustomSwitch) { print(sender.isOn) } -``` \ No newline at end of file +``` From 99e4dc9c7f92f895ededd5181e77eb772fa655f1 Mon Sep 17 00:00:00 2001 From: Alexey Kosov Date: Fri, 13 Sep 2024 21:22:34 +0700 Subject: [PATCH 4/4] remove Autolocalization library --- Package.swift | 7 +------ TechDocs/uikit_utils.md | 9 ++++----- Utils/ActionableLabel/ActionableLabel.swift | 15 ++++----------- .../xcshareddata/swiftpm/Package.resolved | 9 --------- .../Presenter/ActionableLabelPresenter.swift | 14 +++++--------- .../View/ActionableLabelViewController.swift | 3 +-- 6 files changed, 15 insertions(+), 42 deletions(-) diff --git a/Package.swift b/Package.swift index 63e6cb1..63bb9e9 100644 --- a/Package.swift +++ b/Package.swift @@ -16,17 +16,12 @@ let package = Package( name: "CryptoSwift", url: "https://github.com/krzyzanowskim/CryptoSwift", .exact("1.5.1") - ), - .package( - name: "Autolocalizable", - url: "https://github.com/surfstudio/Autolocalizable.git", - from: "1.1.0" ) ], targets: [ .target( name: "Utils", - dependencies: ["CryptoSwift", "Autolocalizable"], + dependencies: ["CryptoSwift"], path: "Utils", exclude: [ "Info.plist" diff --git a/TechDocs/uikit_utils.md b/TechDocs/uikit_utils.md index 9f10c0b..252a497 100644 --- a/TechDocs/uikit_utils.md +++ b/TechDocs/uikit_utils.md @@ -21,13 +21,12 @@ ### ActionableLabel Лэйбл который можно собирать из текстов с разными аттрибутами, так же обрабатывает нажатия, например ссылки. -LocalizableStringItem - принимает в себя простую строку. Пример: Во вью ```Swift -typealias Model = [(text: LocalizableStringItem, didSelect: (()-> Void)?)] +typealias Model = [(text: String, didSelect: (()-> Void)?)] func configure(with model: Model) { label.clear() @@ -47,9 +46,9 @@ func configure(with model: Model) { ```Swift view?.configure(with: [ - (text: LocalizableStringItem("Start sentences "), didSelect: nil), - (text: LocalizableStringItem("link text"), didSelect: { print("link selected") }), - (text: LocalizableStringItem(" end sentences."), didSelect: nil) + (text: "Start sentences ", didSelect: nil), + (text: "link text", didSelect: { print("link selected") }), + (text: " end sentences.", didSelect: nil) ]) ``` diff --git a/Utils/ActionableLabel/ActionableLabel.swift b/Utils/ActionableLabel/ActionableLabel.swift index bc51be0..3324268 100644 --- a/Utils/ActionableLabel/ActionableLabel.swift +++ b/Utils/ActionableLabel/ActionableLabel.swift @@ -6,7 +6,6 @@ // Copyright © 2019 Surf. All rights reserved. // -import Autolocalizable import UIKit public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, ActionableLabelParameters { @@ -16,7 +15,7 @@ public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, Actionab public typealias TextAction = () -> Void public typealias Part = ( - text: LocalizableStringItem, + text: String, attributes: [StringAttribute], isHighlighted: Bool, action: TextAction? @@ -94,7 +93,7 @@ public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, Actionab attributedText = nil } - public func append(text: LocalizableStringItem, attributes: [StringAttribute] = [], action: TextAction? = nil) { + public func append(text: String, attributes: [StringAttribute] = [], action: TextAction? = nil) { parts.append((text: text, attributes: attributes, isHighlighted: false, action: action)) reload() } @@ -113,12 +112,6 @@ public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, Actionab textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = lineBreakMode isUserInteractionEnabled = true - - // Localization - registration(key: localizableKey, item: LocalizableStringItem()) { [weak self] _, _ in - self?.reload() - self?.layoutIfNeeded() - } } private func textOrigin(inRect rect: CGRect) -> CGPoint { @@ -146,7 +139,7 @@ public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, Actionab : foregroundColor currenAttributes.append(.foregroundColor(color)) } - builder.add(.string(part.text.value), with: currenAttributes) + builder.add(.string(part.text), with: currenAttributes) } let attributedString = builder.value @@ -177,7 +170,7 @@ public final class ActionableLabel: UILabel, ViewAccessibilityProtocol, Actionab let index = layoutManager.glyphIndex(for: correctLocation, in: textContainer) - let range = (text as NSString).range(of: part.text.value) + let range = (text as NSString).range(of: part.text) return index >= range.location && index <= range.location + range.length } diff --git a/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 912c28b..ce9102b 100644 --- a/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UtilsExample/UtilsExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "Autolocalizable", - "repositoryURL": "https://github.com/surfstudio/Autolocalizable.git", - "state": { - "branch": null, - "revision": "a78a12a7b2d8dab688365f0ec7504d0bbaf838f1", - "version": "1.1.0" - } - }, { "package": "CryptoSwift", "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift", diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift index 3b49164..9eeda54 100644 --- a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/Presenter/ActionableLabelPresenter.swift @@ -6,35 +6,31 @@ // Copyright © 2022 Surf. All rights reserved. // -import Autolocalizable - final class ActionableLabelPresenter: ActionableLabelModuleOutput { - // MARK: - BrightSideModuleOutput - // MARK: - Properties weak var view: ActionableLabelViewInput? } -// MARK: - BrightSideModuleInput +// MARK: - ActionableLabelModuleInput extension ActionableLabelPresenter: ActionableLabelModuleInput { } -// MARK: - BrightSideViewOutput +// MARK: - ActionableLabelViewOutput extension ActionableLabelPresenter: ActionableLabelViewOutput { func viewLoaded() { view?.setupInitialState() view?.configure(with: [ - (text: LocalizableStringItem("Start sentences "), didSelect: nil), - (text: LocalizableStringItem("link text"), didSelect: { + (text: "Start sentences ", didSelect: nil), + (text: "link text", didSelect: { print("link selected") }), - (text: LocalizableStringItem(" end sentences."), didSelect: nil) + (text: " end sentences.", didSelect: nil) ]) } diff --git a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift index de485e7..511efb7 100644 --- a/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift +++ b/UtilsExample/UtilsExample/Playbook/Flows/ActionableLabel/View/ActionableLabelViewController.swift @@ -8,13 +8,12 @@ import UIKit import Utils -import Autolocalizable final class ActionableLabelViewController: UIViewController { // MARK: - Nested types - public typealias Model = [(text: LocalizableStringItem, didSelect: (() -> Void)?)] + public typealias Model = [(text: String, didSelect: (() -> Void)?)] // MARK: - IBOutlets