diff --git a/.gitignore b/.gitignore index 9004ef49..835c021b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,18 @@ -# ================ -# Swift.gitignore -# ================ - # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore -## Build generated -build/ -DerivedData +## User settings +xcuserdata/ -## Various settings +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside *.pbxuser !default.pbxuser *.mode1v3 @@ -19,17 +21,14 @@ DerivedData !default.mode2v3 *.perspectivev3 !default.perspectivev3 -xcuserdata - -## Other -*.xccheckout -*.moved-aside -*.xcuserstate -*.xcscmblueprint ## Obj-C/Swift specific *.hmap + +## App packaging *.ipa +*.dSYM.zip +*.dSYM ## Playgrounds timeline.xctimeline @@ -39,6 +38,14 @@ playground.xcworkspace # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm + .build/ # CocoaPods @@ -48,33 +55,51 @@ playground.xcworkspace # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts -Carthage/Build +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ # fastlane # -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: -# https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md +# https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml -fastlane/screenshots +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode -# AppCode -# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems +iOSInjectionProject/ -.idea/workspace.xml -.idea/tasks.xml +# homebrew-bundle +Brewfile.lock.json -cpd-output.xml +# Node.js +# Dependency directories +node_modules/ # Touch Instinct custom Downloads/ - +fastlane/README.md +Templates/ +cpd-output.xml +*.swp +*IDEWorkspaceChecks.plist diff --git a/CHANGELOG.md b/CHANGELOG.md index 80043749..1e06c8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### 0.11.0 +- **Add**: Cocoapods support for TI-family libraries. +- **Add**: `SeparatorConfigurable` and all helper types for separator configuration. +- **Add**: `BaseSeparatorCell` - `BaseInitializeableCell` subclass with separators support. +- **Add**: `TITableKitUtils` - set of helpers for TableKit classes. +- **Add**: `BaseTextAttributes` and `ViewText` implementation form LeadKit. +- **Update**: `BaseInitializableView` and `BaseInitializableControl` are moved to `TIUIElements` from `TIUIKitCore`. + ### 0.10.9 - **Fix**: `change presentedOrTopViewController to open`. diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..7c8c1236 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "TableKit", + "repositoryURL": "https://github.com/maxsokolov/TableKit.git", + "state": { + "branch": null, + "revision": "8bf4840d9d0475a92352f02f368f88b74eced447", + "version": "2.11.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 698d8992..7e976793 100644 --- a/Package.swift +++ b/Package.swift @@ -12,14 +12,19 @@ let package = Package( .library(name: "TISwiftUtils", targets: ["TISwiftUtils"]), .library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]), .library(name: "TIUIElements", targets: ["TIUIElements"]), + .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]) ], + dependencies: [ + .package(url: "https://github.com/maxsokolov/TableKit.git", from: "2.11.0") + ], targets: [ .target(name: "TITransitions", path: "TITransitions/Sources"), .target(name: "TIUIKitCore", path: "TIUIKitCore/Sources"), .target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"), .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils/Sources"), .target(name: "TIUIElements", dependencies: ["TIUIKitCore"], path: "TIUIElements/Sources"), + .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "OTPSwiftView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "OTPSwiftView/Sources") ] ) diff --git a/README.md b/README.md index 6cda623b..73a91c28 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,25 @@ This repository contains the following additional frameworks: - [TIUIElements](TIUIElements) - bunch of of useful protocols and views. - [OTPSwiftView](OTPSwiftView) - a fully customizable OTP view. - [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for development. +- [TITableKitUtils](TITableKitUtils) - Set of helpers for TableKit classes. + +## Installation + +### SPM + +```swift +dependencies: [ + .package(url: "https://github.com/TouchInstinct/LeadKit.git", from: "x.y.z"), +], +``` + +### Cocoapods + +```ruby +source 'https://github.com/TouchInstinct/Podspecs.git' + +pod 'TISwiftUtils', 'x.y.z' +pod 'TIFoundationUtils', 'x.y.z' +# ... +``` diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec new file mode 100644 index 00000000..384ae080 --- /dev/null +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + s.name = 'TIFoundationUtils' + s.version = '0.11.0' + s.summary = 'Set of helpers for Foundation framework classes.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TISwiftUtils', s.version.to_s + s.framework = 'Foundation' +end diff --git a/TISwiftUtils/README.md b/TISwiftUtils/README.md index 993c9cfe..1a9f1a94 100644 --- a/TISwiftUtils/README.md +++ b/TISwiftUtils/README.md @@ -18,7 +18,3 @@ final class ViewModel { var hasFinishedOnboarding: Bool } ``` - -# Installation via SPM - -You can install this framework as a target of LeadKit. diff --git a/TISwiftUtils/Sources/Extensions/Optional/Optional+Extensions.swift b/TISwiftUtils/Sources/Extensions/Optional/Optional+Extensions.swift index 0bddd3bb..af793186 100644 --- a/TISwiftUtils/Sources/Extensions/Optional/Optional+Extensions.swift +++ b/TISwiftUtils/Sources/Extensions/Optional/Optional+Extensions.swift @@ -20,8 +20,6 @@ // THE SOFTWARE. // -import UIKit - public extension Optional where Wrapped == String { var orEmpty: String { self ?? "" diff --git a/TISwiftUtils/Sources/Extensions/Substring/Substring+Extensions.swift b/TISwiftUtils/Sources/Extensions/Substring/Substring+Extensions.swift index 61d0812f..e68fc6f3 100644 --- a/TISwiftUtils/Sources/Extensions/Substring/Substring+Extensions.swift +++ b/TISwiftUtils/Sources/Extensions/Substring/Substring+Extensions.swift @@ -20,8 +20,6 @@ // THE SOFTWARE. // -import Foundation - public extension Substring { var string: String { String(self) diff --git a/TISwiftUtils/Sources/Helpers/Typealias.swift b/TISwiftUtils/Sources/Helpers/Typealias.swift index 897d1f09..3ece5296 100644 --- a/TISwiftUtils/Sources/Helpers/Typealias.swift +++ b/TISwiftUtils/Sources/Helpers/Typealias.swift @@ -20,8 +20,6 @@ // THE SOFTWARE. // -import UIKit - /// Closure with custom arguments and return value. public typealias Closure = (Input) -> Output diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec new file mode 100644 index 00000000..4a256658 --- /dev/null +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |s| + s.name = 'TISwiftUtils' + s.version = '0.11.0' + s.summary = 'Bunch of useful helpers for Swift development.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '9.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' +end diff --git a/TITableKitUtils/README.md b/TITableKitUtils/README.md new file mode 100644 index 00000000..1401a1b5 --- /dev/null +++ b/TITableKitUtils/README.md @@ -0,0 +1,4 @@ +# TITableKitUtils + +Set of helpers for TableKit classes. + diff --git a/TITableKitUtils/Sources/Extensions/TableDirector/TableDirector+Extensions.swift b/TITableKitUtils/Sources/Extensions/TableDirector/TableDirector+Extensions.swift new file mode 100644 index 00000000..4156c963 --- /dev/null +++ b/TITableKitUtils/Sources/Extensions/TableDirector/TableDirector+Extensions.swift @@ -0,0 +1,243 @@ +// +// Copyright (c) 2020 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 TableKit +import UIKit.UITableView + +public extension TableDirector { + + /** + method replaces current table director's section at index and reloads it + + - parameter section: new section + - parameter index: current replaced section index + - parameter reload: is reloaded after replace + + - returns: self + */ + @discardableResult + func replace(section: TableSection, atIndex index: Int, reload: Bool = true) -> Self { + if index < sections.count { + remove(sectionAt: index) + } + insert(section: section, atIndex: index) + if reload { + self.reload(sectionAtIndex: index) + } + return self + } + + /** + method reloads section at index with animation + + - parameter index: current reloaded section index + - parameter animation: reloading animation. Default .none + + - returns: self + */ + @discardableResult + func reload(sectionAtIndex index: Int, with animation: UITableView.RowAnimation = .none) -> Self { + let action = { [tableView] in + guard let tableView = tableView else { + return + } + + if index < tableView.numberOfSections { + tableView.reloadSections([index], with: animation) + } else { + tableView.reloadData() + } + } + if animation == .none { + UIView.performWithoutAnimation(action) + } else { + action() + } + return self + } + + /** + method replaces current table director's state with sections + + - parameter sections: new sections + + - returns: self + */ + @discardableResult + func replace(withSections sections: [TableSection]) -> Self { + clear().append(sections: sections).reload() + return self + } + + /** + method replaces current table director's state with section + + - parameter section: new section + + - returns: self + */ + @discardableResult + func replace(withSection section: TableSection) -> Self { + return replace(withSections: [section]) + } + + /** + method replaces current table director's state with rows + + - parameter rows: new rows + + - returns: self + */ + @discardableResult + func replace(withRows rows: [Row]) -> Self { + return replace(withSection: TableSection(rows: rows)) + } + + /// Clear table view and reload it within empty section + func safeClear() { + clear().append(section: TableSection(onlyRows: [])).reload() + } + + /// Inserts rows into table without complete reload. + /// + /// - Parameters: + /// - rows: Rows to insert. + /// - indexPath: Position of first row. + /// - animation: The type of animation when rows are inserted + /// - manualBeginEndUpdates: Don't call beginUpdates() & endUpdates() inside. + func insert(rows: [Row], + at indexPath: IndexPath, + with animation: UITableView.RowAnimation, + manualBeginEndUpdates: Bool = false) { + + sections[indexPath.section].insert(rows: rows, at: indexPath.row) + let indexPaths: [IndexPath] = rows.indices.map { + IndexPath(row: indexPath.row + $0, section: indexPath.section) + } + + if manualBeginEndUpdates { + tableView?.insertRows(at: indexPaths, with: animation) + } else { + tableView?.beginUpdates() + tableView?.insertRows(at: indexPaths, with: animation) + tableView?.endUpdates() + } + } + + /// Removes rows from table without complete reload. + /// + /// - Parameters: + /// - rowsCount: Number of rows to remove. + /// - indexPath: Position of first row to remove. + /// - animation: The type of animation when rows are deleted + /// - manualBeginEndUpdates: Don't call beginUpdates() & endUpdates() inside. + func remove(rowsCount: Int, + startingAt indexPath: IndexPath, + with animation: UITableView.RowAnimation, + manualBeginEndUpdates: Bool = false) { + + var indexPaths = [IndexPath]() + for index in indexPath.row ..< indexPath.row + rowsCount { + indexPaths.append(IndexPath(row: index, section: indexPath.section)) + } + + indexPaths.reversed().forEach { + sections[$0.section].remove(rowAt: $0.row) + } + + if manualBeginEndUpdates { + tableView?.deleteRows(at: indexPaths, with: animation) + } else { + tableView?.beginUpdates() + tableView?.deleteRows(at: indexPaths, with: animation) + tableView?.endUpdates() + } + } + + /// Method inserts section with animation. + /// + /// - Parameters: + /// - section: Section to insert + /// - index: Position to insert + /// - animation: The type of insert animation + /// - manualBeginEndUpdates: Don't call beginUpdates() & endUpdates() inside. + /// - Returns: self + @discardableResult + func insert(section: TableSection, + at index: Int, + with animation: UITableView.RowAnimation, + manualBeginEndUpdates: Bool = false) -> Self { + + insert(section: section, atIndex: index) + if manualBeginEndUpdates { + tableView?.insertSections([index], with: animation) + } else { + tableView?.beginUpdates() + tableView?.insertSections([index], with: animation) + tableView?.endUpdates() + } + + return self + } + + /// Method removes section with animation. + /// + /// - Parameters: + /// - index: Position to remove + /// - animation: The type of remove animation + /// - manualBeginEndUpdates: Don't call beginUpdates() & endUpdates() inside. + /// - Returns: self + @discardableResult + func remove(at index: Int, + with animation: UITableView.RowAnimation, + manualBeginEndUpdates: Bool = false) -> Self { + + delete(sectionAt: index) + if manualBeginEndUpdates { + tableView?.deleteSections([index], with: animation) + } else { + tableView?.beginUpdates() + tableView?.deleteSections([index], with: animation) + tableView?.endUpdates() + } + + return self + } + + /// Method replace section with animation. + /// + /// - Parameters: + /// - section: Section to replace + /// - index: Position to replace + /// - animation: The type of replace animation + /// - manualBeginEndUpdates: Don't call beginUpdates() & endUpdates() inside. + /// - Returns: self + @discardableResult + func replace(with section: TableSection, + at index: Int, + with animation: UITableView.RowAnimation, + manualBeginEndUpdates: Bool = false) -> Self { + + remove(at: index, with: animation, manualBeginEndUpdates: manualBeginEndUpdates) + return insert(section: section, at: index, with: animation, manualBeginEndUpdates: manualBeginEndUpdates) + } +} diff --git a/TITableKitUtils/Sources/Extensions/TableSection/TableSection+Extensions.swift b/TITableKitUtils/Sources/Extensions/TableSection/TableSection+Extensions.swift new file mode 100644 index 00000000..eda00808 --- /dev/null +++ b/TITableKitUtils/Sources/Extensions/TableSection/TableSection+Extensions.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2020 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 TableKit + +public extension TableSection { + + /// Initializes section with rows and zero height footer and header. + /// + /// - Parameter rows: Rows to insert into section. + convenience init(onlyRows rows: [Row]) { + self.init(rows: rows) + + self.headerView = nil + self.footerView = nil + + self.headerHeight = .leastNonzeroMagnitude + self.footerHeight = .leastNonzeroMagnitude + } +} diff --git a/TITableKitUtils/Sources/Separators/Extensions/Array/Array+SeparatorRowBox.swift b/TITableKitUtils/Sources/Separators/Extensions/Array/Array+SeparatorRowBox.swift new file mode 100644 index 00000000..fa212ca2 --- /dev/null +++ b/TITableKitUtils/Sources/Separators/Extensions/Array/Array+SeparatorRowBox.swift @@ -0,0 +1,66 @@ +// +// Copyright (c) 2020 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 TableKit +import TIUIElements + +public extension Array where Element == SeparatorRowBox { + + /// Create rows from SeparatorRowBox array + var rows: [Row] { + return map { $0.row } + } + + /// Configure separators from SeparatorRowBox array + /// - parameter extreme: Configuration that will be used for extreme values, for first or last row + /// - parameter middle: Configuration for intermediate rows + func configureSeparators(extreme extremeSeparatorConfiguration: SeparatorConfiguration, + middle middleSeparatorConfiguration: SeparatorConfiguration) { + + configureSeparators(first: extremeSeparatorConfiguration, + middle: middleSeparatorConfiguration, + last: extremeSeparatorConfiguration) + } + + /// Configure separators from SeparatorRowBox array + /// - parameter first: Configuration of the top separator of the first row + /// - parameter middle: Configuration of the separators between the rows + /// - parameter last: Configuration of the bottom separator of the last row + func configureSeparators(first firstSeparatorConfiguration: SeparatorConfiguration, + middle middleSeparatorConfiguration: SeparatorConfiguration, + last lastSeparatorConfiguration: SeparatorConfiguration) { + + if isEmpty { + return + } + + switch count { + case 1: + first?.set(separatorType: .full(firstSeparatorConfiguration, lastSeparatorConfiguration)) + + default: + dropFirst().dropLast().forEach { $0.set(separatorType: .bottom(middleSeparatorConfiguration)) } + first?.set(separatorType: .full(firstSeparatorConfiguration, middleSeparatorConfiguration)) + last?.set(separatorType: .bottom(lastSeparatorConfiguration)) + } + } +} diff --git a/TITableKitUtils/Sources/Separators/Extensions/TableRow/TableRow+Separators.swift b/TITableKitUtils/Sources/Separators/Extensions/TableRow/TableRow+Separators.swift new file mode 100644 index 00000000..05ae4742 --- /dev/null +++ b/TITableKitUtils/Sources/Separators/Extensions/TableRow/TableRow+Separators.swift @@ -0,0 +1,53 @@ +// +// Copyright (c) 2020 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 TableKit +import TIUIElements + +private let configureSeparatorActionId = "TableRowConfigureSeparatorActionId" + +public extension TableRow where CellType: SeparatorConfigurable { + + func with(separatorType: ViewSeparatorType) -> Self { + set(separatorType: separatorType) + return self + } + + func set(separatorType: ViewSeparatorType) { + removeAction(forActionId: configureSeparatorActionId) + + let action = TableRowAction(.configure) { + $0.cell?.configureSeparators(with: separatorType) + } + + action.id = configureSeparatorActionId + on(action) + } +} + +public extension TableRow where CellType: SeparatorConfigurable { + + /// TableRow typed as SeparatorRowBox + var separatorRowBox: SeparatorRowBox { + return SeparatorRowBox(row: self) + } +} diff --git a/TITableKitUtils/Sources/Separators/SeparatorRowBox.swift b/TITableKitUtils/Sources/Separators/SeparatorRowBox.swift new file mode 100644 index 00000000..6109c4da --- /dev/null +++ b/TITableKitUtils/Sources/Separators/SeparatorRowBox.swift @@ -0,0 +1,41 @@ +// +// Copyright (c) 2020 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 TableKit +import TIUIElements +import TISwiftUtils + +/// Class that used to configure separators when multiply cells presented in one section +public final class SeparatorRowBox { + private let setSeparatorHandler: ParameterClosure + + public func set(separatorType: ViewSeparatorType) { + setSeparatorHandler(separatorType) + } + + public let row: Row + + public init(row: TableRow) where T: SeparatorConfigurable { + self.row = row + setSeparatorHandler = row.set + } +} diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec new file mode 100644 index 00000000..3cacb356 --- /dev/null +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -0,0 +1,18 @@ +Pod::Spec.new do |s| + s.name = 'TITableKitUtils' + s.version = '0.11.0' + s.summary = 'Set of helpers for TableKit classes.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TIUIElements', s.version.to_s + s.dependency 'TISwiftUtils', s.version.to_s + s.dependency 'TableKit', '2.11.0' +end diff --git a/TIUIElements/Sources/Separators/BaseSeparatorCell.swift b/TIUIElements/Sources/Separators/BaseSeparatorCell.swift new file mode 100644 index 00000000..003f7dd0 --- /dev/null +++ b/TIUIElements/Sources/Separators/BaseSeparatorCell.swift @@ -0,0 +1,147 @@ +// +// Copyright (c) 2020 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 + +open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable { + private lazy var topSeparatorView = createTopSeparator() + private lazy var bottomSeparatorView = UIView() + + private var topViewLeftConstraint: NSLayoutConstraint? + private var topViewRightConstraint: NSLayoutConstraint? + private var topViewTopConstraint: NSLayoutConstraint? + private var topViewHeightConstraint: NSLayoutConstraint? + + private var bottomViewLeftConstraint: NSLayoutConstraint? + private var bottomViewRightConstraint: NSLayoutConstraint? + private var bottomViewBottomConstraint: NSLayoutConstraint? + private var bottomViewHeightConstraint: NSLayoutConstraint? + + open func createTopSeparator() -> UIView { + .init() + } + + open func createBottomSeparator() -> UIView { + .init() + } + + open func add(topSeparatorView: UIView) { + contentView.addSubview(topSeparatorView) + } + + open func add(bottomSeparatorView: UIView) { + contentView.addSubview(bottomSeparatorView) + } + + public func configureSeparators(with separatorType: ViewSeparatorType) { + topSeparatorView.isHidden = separatorType.topIsHidden + bottomSeparatorView.isHidden = separatorType.bottomIsHidden + + switch separatorType { + case .none: + break + + case let .bottom(configuration): + updateBottomSeparator(with: configuration) + + case let .top(configuration): + updateTopSeparator(with: configuration) + + case let .full(topConfiguration, bottomConfiguration): + updateTopSeparator(with: topConfiguration) + updateBottomSeparator(with: bottomConfiguration) + } + } + + open override func prepareForReuse() { + super.prepareForReuse() + configureSeparators(with: .none) + } + + // MARK: - InitializableView + + open override func addViews() { + super.addViews() + + add(topSeparatorView: topSeparatorView) + add(bottomSeparatorView: bottomSeparatorView) + } + + open override func configureLayout() { + super.configureLayout() + + if let separatorSuperview = topSeparatorView.superview { + topViewTopConstraint = topSeparatorView.topAnchor.constraint(equalTo: separatorSuperview.topAnchor) + topViewRightConstraint = separatorSuperview.rightAnchor.constraint(equalTo: topSeparatorView.rightAnchor) + topViewLeftConstraint = topSeparatorView.leftAnchor.constraint(equalTo: separatorSuperview.leftAnchor) + } + + topViewHeightConstraint = topSeparatorView.heightAnchor.constraint(equalToConstant: 1) + + if let separatorSuperview = topSeparatorView.superview { + bottomViewRightConstraint = separatorSuperview.rightAnchor.constraint(equalTo: bottomSeparatorView.rightAnchor) + bottomViewLeftConstraint = bottomSeparatorView.leftAnchor.constraint(equalTo: separatorSuperview.leftAnchor) + bottomViewBottomConstraint = bottomSeparatorView.bottomAnchor.constraint(equalTo: separatorSuperview.bottomAnchor) + } + + bottomViewHeightConstraint = bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1) + + NSLayoutConstraint.activate([ + topViewTopConstraint, + topViewRightConstraint, + topViewLeftConstraint, + topViewHeightConstraint, + bottomViewRightConstraint, + bottomViewLeftConstraint, + bottomViewBottomConstraint, + bottomViewHeightConstraint + ].compactMap { $0 }) + } + + open override func configureAppearance() { + super.configureAppearance() + + [topSeparatorView, bottomSeparatorView].forEach { + $0.isHidden = true + $0.backgroundColor = .black + $0.translatesAutoresizingMaskIntoConstraints = false + } + } +} + +private extension BaseSeparatorCell { + func updateTopSeparator(with configuration: SeparatorConfiguration) { + topSeparatorView.backgroundColor = configuration.color + topViewHeightConstraint?.constant = configuration.height + topViewTopConstraint?.constant = configuration.insets.top + topViewLeftConstraint?.constant = configuration.insets.left + topViewRightConstraint?.constant = -configuration.insets.right + } + + func updateBottomSeparator(with configuration: SeparatorConfiguration) { + bottomSeparatorView.backgroundColor = configuration.color + bottomViewHeightConstraint?.constant = configuration.height + bottomViewBottomConstraint?.constant = -configuration.insets.bottom + bottomViewLeftConstraint?.constant = configuration.insets.left + bottomViewRightConstraint?.constant = -configuration.insets.right + } +} diff --git a/TIUIElements/Sources/Separators/SeparatorConfigurable.swift b/TIUIElements/Sources/Separators/SeparatorConfigurable.swift new file mode 100644 index 00000000..6e58a390 --- /dev/null +++ b/TIUIElements/Sources/Separators/SeparatorConfigurable.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2020 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. +// + +public protocol SeparatorConfigurable { + func configureSeparators(with separatorType: ViewSeparatorType) +} diff --git a/TIUIElements/Sources/Separators/SeparatorConfiguration.swift b/TIUIElements/Sources/Separators/SeparatorConfiguration.swift new file mode 100644 index 00000000..0a44231c --- /dev/null +++ b/TIUIElements/Sources/Separators/SeparatorConfiguration.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2020 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 + +public struct SeparatorConfiguration { + + public let color: UIColor + public let insets: UIEdgeInsets + public let height: CGFloat + + public init(color: UIColor, insets: UIEdgeInsets = .zero, height: CGFloat = 1) { + self.color = color + self.insets = insets + self.height = height + } +} diff --git a/TIUIElements/Sources/Separators/ViewSeparatorType.swift b/TIUIElements/Sources/Separators/ViewSeparatorType.swift new file mode 100644 index 00000000..9158ff2e --- /dev/null +++ b/TIUIElements/Sources/Separators/ViewSeparatorType.swift @@ -0,0 +1,71 @@ +// +// Copyright (c) 2020 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. +// + +public enum ViewSeparatorType { + + /// All separators for view is hidden + case none + + /// Show only top separator + case top(SeparatorConfiguration) + + /// Show only bottom separator + case bottom(SeparatorConfiguration) + + /// First configuration for top, second for bottom + case full(SeparatorConfiguration, SeparatorConfiguration) +} + +public extension ViewSeparatorType { + + /// Determine if bottom separator is hidden. + var bottomIsHidden: Bool { + return bottomConfiguration == nil + } + + /// Determine if top separator is hidden. + var topIsHidden: Bool { + return topConfiguration == nil + } + + /// Returns top configuration if type is top or full. + var topConfiguration: SeparatorConfiguration? { + switch self { + case let .top(configuration), let .full(configuration, _): + return configuration + + default: + return nil + } + } + + /// Returns bottom configuration if type is bottom or full. + var bottomConfiguration: SeparatorConfiguration? { + switch self { + case let .bottom(configuration), let .full(_, configuration): + return configuration + + default: + return nil + } + } +} diff --git a/TIUIKitCore/Sources/Views/BaseInitializableControl.swift b/TIUIElements/Sources/Views/BaseInitializableControl.swift similarity index 95% rename from TIUIKitCore/Sources/Views/BaseInitializableControl.swift rename to TIUIElements/Sources/Views/BaseInitializableControl.swift index c5cc67f0..7d852d5d 100644 --- a/TIUIKitCore/Sources/Views/BaseInitializableControl.swift +++ b/TIUIElements/Sources/Views/BaseInitializableControl.swift @@ -1,6 +1,7 @@ import UIKit +import TIUIKitCore -open class BaseInitializableControl: UIControl, InitializableView { +open class BaseInitializableControl: UIControl, InitializableViewProtocol { override public init(frame: CGRect) { super.init(frame: frame) diff --git a/TIUIKitCore/Sources/Views/BaseInitializableView.swift b/TIUIElements/Sources/Views/BaseInitializableView.swift similarity index 95% rename from TIUIKitCore/Sources/Views/BaseInitializableView.swift rename to TIUIElements/Sources/Views/BaseInitializableView.swift index 1fb7d960..d7192498 100644 --- a/TIUIKitCore/Sources/Views/BaseInitializableView.swift +++ b/TIUIElements/Sources/Views/BaseInitializableView.swift @@ -21,8 +21,9 @@ // import UIKit +import TIUIKitCore -open class BaseInitializableView: UIView, InitializableView { +open class BaseInitializableView: UIView, InitializableViewProtocol { override public init(frame: CGRect) { super.init(frame: frame) diff --git a/TIUIElements/Sources/Views/Cells/BaseInitializableCell.swift b/TIUIElements/Sources/Views/Cells/BaseInitializableCell.swift new file mode 100644 index 00000000..7f45b55f --- /dev/null +++ b/TIUIElements/Sources/Views/Cells/BaseInitializableCell.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2020 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 TIUIKitCore +import UIKit + +open class BaseInitializableCell: UITableViewCell, InitializableViewProtocol { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .default, reuseIdentifier: reuseIdentifier) + + initializeView() + } + + @available(*, unavailable) + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + // MARK: - InitializableView + + open func addViews() { + // empty for subclasses overriding + } + + open func bindViews() { + // empty for subclasses overriding + } + + open func configureLayout() { + // empty for subclasses overriding + } + + open func configureAppearance() { + selectionStyle = .none + backgroundColor = .clear + } + + open func localize() { + // empty for subclasses overriding + } +} diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec new file mode 100644 index 00000000..74097f9d --- /dev/null +++ b/TIUIElements/TIUIElements.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + s.name = 'TIUIElements' + s.version = '0.11.0' + s.summary = 'Bunch of useful protocols and views.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TIUIKitCore', s.version.to_s +end diff --git a/TIUIKitCore/README.md b/TIUIKitCore/README.md index 9c46b9b3..96d028a7 100644 --- a/TIUIKitCore/README.md +++ b/TIUIKitCore/README.md @@ -12,7 +12,3 @@ Core UI elements: protocols, views and helpers. # Views - [BaseInitializableView](BaseInitializableView/BaseInitializableView.swift) - UIView conformance to InitializableView. - -# Installation via SPM - -You can install this framework as a target of LeadKit. diff --git a/TIUIKitCore/Sources/Extensions/InitializableView/InitializableView+Extensions.swift b/TIUIKitCore/Sources/Extensions/InitializableView/InitializableView+Extensions.swift index b5026ee6..2c10e7c9 100644 --- a/TIUIKitCore/Sources/Extensions/InitializableView/InitializableView+Extensions.swift +++ b/TIUIKitCore/Sources/Extensions/InitializableView/InitializableView+Extensions.swift @@ -20,7 +20,7 @@ // THE SOFTWARE. // -public extension InitializableView { +public extension InitializableViewProtocol { func initializeView() { addViews() diff --git a/TIUIKitCore/Sources/Protocols/ConfigurableView/ConfigurableView.swift b/TIUIKitCore/Sources/Protocols/ConfigurableView/ConfigurableView.swift new file mode 100644 index 00000000..cd5fc1ab --- /dev/null +++ b/TIUIKitCore/Sources/Protocols/ConfigurableView/ConfigurableView.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 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. +// + +public protocol ConfigurableView { + associatedtype ViewModelType + + func configure(with _: ViewModelType) +} diff --git a/TIUIKitCore/Sources/Protocols/InitializableView/InitializableView.swift b/TIUIKitCore/Sources/Protocols/InitializableViewProtocol/InitializableViewProtocol.swift similarity index 97% rename from TIUIKitCore/Sources/Protocols/InitializableView/InitializableView.swift rename to TIUIKitCore/Sources/Protocols/InitializableViewProtocol/InitializableViewProtocol.swift index bbf07d29..766256fb 100644 --- a/TIUIKitCore/Sources/Protocols/InitializableView/InitializableView.swift +++ b/TIUIKitCore/Sources/Protocols/InitializableViewProtocol/InitializableViewProtocol.swift @@ -21,7 +21,7 @@ // /// Protocol with methods that should be called in constructor methods of view. -public protocol InitializableView { +public protocol InitializableViewProtocol { /// Main method that should call other methods in particular order. func initializeView() diff --git a/TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift b/TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift new file mode 100644 index 00000000..d9d5a26e --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2020 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.UIFont +import UIKit.UIColor + +/// Base set of attributes to configure appearance of text. +open class BaseTextAttributes { + + /// Text font. + public let font: UIFont + /// Text color. + public let color: UIColor + /// Text alignment. + public let alignment: NSTextAlignment + + /// Memberwise initializer. + /// + /// - Parameters: + /// - font: Text font. + /// - color: Text color. + /// - alignment: Text alignment. + public init(font: UIFont, color: UIColor, alignment: NSTextAlignment = .natural) { + self.font = font + self.color = color + self.alignment = alignment + } +} + +public extension BaseTextAttributes { + + /// Configures text appearance of given ViewTextConfigurable instance. + /// + /// - Parameter view: ViewTextConfigurable instance to configure with BaseTextAttributes. + func configureBaseApperance(of view: ViewTextConfigurable) { + view.configureBaseAppearance(with: self) + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewText.swift b/TIUIKitCore/Sources/ViewText/ViewText.swift new file mode 100644 index 00000000..b84ea370 --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/ViewText.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) 2020 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 + +/// Enum that describes text with appearance options. +/// +/// - string: Regular string with common and often-used text attributes. +/// - attributedString: Attributed string. +public enum ViewText { + + case string(String, textAttributes: BaseTextAttributes) + case attributedString(NSAttributedString) +} + +public extension ViewText { + + /// Convenient initializer for .string case with default alignment parameter. + /// + /// - Parameters: + /// - string: Text to use. + /// - font: Font to use. + /// - color: Color to use. + /// - alignment: Alignment to use. Default is natural. + init(string: String, font: UIFont, color: UIColor, alignment: NSTextAlignment = .natural) { + self = .string(string, textAttributes: BaseTextAttributes(font: font, + color: color, + alignment: alignment)) + } + + /// Attributed string created using text attributes. + var attributedString: NSAttributedString { + switch self { + case let .string(title, textAttributes): + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = textAttributes.alignment + + let attributes: [NSAttributedString.Key: Any] = [ + .font: textAttributes.font, + .foregroundColor: textAttributes.color, + .paragraphStyle: paragraphStyle + ] + + return NSAttributedString(string: title, attributes: attributes) + + case .attributedString(let attributedTitle): + return attributedTitle + } + } + + /// Method that calculates size of view text using given max width and height arguments. + /// + /// - Parameters: + /// - maxWidth: The width constraint to apply when computing the string’s bounding rectangle. + /// - maxHeight: The width constraint to apply when computing the string’s bounding rectangle. + /// - Returns: Returns the size required to draw the text. + func size(maxWidth: CGFloat = CGFloat.greatestFiniteMagnitude, + maxHeight: CGFloat = CGFloat.greatestFiniteMagnitude) -> CGSize { + + return attributedString.boundingRect(with: CGSize(width: maxWidth, height: maxHeight), + options: [.usesLineFragmentOrigin, .usesFontLeading], + context: nil).size + } + + /// Configures given ViewTextConfigurable instance. + /// + /// - Parameter view: ViewTextConfigurable instance to configure with ViewText. + func configure(view: ViewTextConfigurable) { + view.configure(with: self) + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift new file mode 100644 index 00000000..de0e5e5f --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift @@ -0,0 +1,121 @@ +// +// Copyright (c) 2020 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.UIButton + +extension UIButton: ViewTextConfigurable { + + public var textFont: UIFont? { + get { + return titleLabel?.font + } + set { + titleLabel?.font = newValue + } + } + + public var titleColor: UIColor? { + get { + return currentTitleColor + } + set { + setTitleColor(newValue, for: []) + } + } + + public var textAlignment: NSTextAlignment { + get { + return contentHorizontalAlignment.textAlignment + } + set { + contentHorizontalAlignment = .init(textAlignment: newValue) + } + } + + public var text: String? { + get { + return currentTitle + } + set { + setTitle(newValue, for: []) + } + } + + public var attributedText: NSAttributedString? { + get { + return currentAttributedTitle + } + set { + setAttributedTitle(newValue, for: []) + } + } +} + +private extension UIControl.ContentHorizontalAlignment { + + init(textAlignment: NSTextAlignment) { + switch textAlignment { + case .left: + self = .leading + + case .right: + self = .trailing + + case .center: + self = .center + + case .justified: + self = .fill + + case .natural: + self = .leading + + @unknown default: + self = .leading + } + } + + var textAlignment: NSTextAlignment { + switch self { + case .left: + return .left + + case .right: + return .right + + case .center: + return .center + + case .fill: + return .justified + + case .leading: + return .natural + + case .trailing: + return .right + + @unknown default: + return .natural + } + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift new file mode 100644 index 00000000..8418ffa4 --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2020 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.UILabel + +extension UILabel: ViewTextConfigurable { + + public var textFont: UIFont? { + get { + return font + } + set { + font = newValue + } + } + + public var titleColor: UIColor? { + get { + return textColor + } + set { + textColor = newValue + } + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift new file mode 100644 index 00000000..eaa2ce79 --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2020 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.UITextField + +extension UITextField: ViewTextConfigurable { + + public var textFont: UIFont? { + get { + return font + } + set { + font = newValue + } + } + + public var titleColor: UIColor? { + get { + return textColor + } + set { + textColor = newValue + } + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift new file mode 100644 index 00000000..dfd7256c --- /dev/null +++ b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2020 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.UIFont +import UIKit.UIColor + +/// Protocol that represents text object with appearance attributes. +public protocol ViewTextConfigurable: AnyObject { + + /// Font of text object. + var textFont: UIFont? { get set } + + /// Text color of text object. + var titleColor: UIColor? { get set } + + /// Text alignment of text object. + var textAlignment: NSTextAlignment { get set } + + /// Text itself of text object. + var text: String? { get set } + + /// Attributed text of text object. + var attributedText: NSAttributedString? { get set } +} + +public extension ViewTextConfigurable { + + /// Configures text and text appearance of view using ViewText object. + /// + /// - Parameter viewText: ViewText object with text and text appearance. + func configure(with viewText: ViewText) { + switch viewText { + case let .string(text, textAttributes): + self.text = text + self.configureBaseAppearance(with: textAttributes) + + case .attributedString(let attributedString): + self.attributedText = attributedString + } + } + + /// Configures text appearance of view. + /// + /// - Parameter baseTextAttributes: Set of attributes to configure appearance of text. + func configureBaseAppearance(with baseTextAttributes: BaseTextAttributes) { + textFont = baseTextAttributes.font + titleColor = baseTextAttributes.color + textAlignment = baseTextAttributes.alignment + } +} diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec new file mode 100644 index 00000000..bd49a66e --- /dev/null +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -0,0 +1,15 @@ +Pod::Spec.new do |s| + s.name = 'TIUIKitCore' + s.version = '0.11.0' + s.summary = 'Core UI elements: protocols, views and helpers.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + s.framework = 'UIKit' +end