diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e4cb2c8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Overseer"] + path = Overseer + url = https://github.com/Kosoku/Overseer.git diff --git a/Demo-iOS/ShakeAnimationViewController.swift b/Demo-iOS/ShakeAnimationViewController.swift index 3577220..c10a4c5 100644 --- a/Demo-iOS/ShakeAnimationViewController.swift +++ b/Demo-iOS/ShakeAnimationViewController.swift @@ -49,17 +49,16 @@ final class ShakeAnimationViewController: UIViewController { self.view.backgroundColor = .systemBackground self.view.addSubview(self.stackView.also { $0.addArrangedSubviews([self.textField, self.button.also { - $0.addTarget(self, action: #selector(buttonAction(sender:)), for: .touchUpInside) + $0.addBlock { [weak self] _, _ in + guard let self else { + return + } + self.textField.startShakeAnimation() + } }]) self.textField.pinToSuperviewEdges([.leading, .trailing], edgeInsets: .zero) }) self.stackView.pinToSuperviewEdges([.leading, .trailing], safeAreaLayoutGuideEdges: .top) } - - // MARK: - Private Functions - @objc - private func buttonAction(sender: UIButton) { - self.textField.startShakeAnimation() - } } diff --git a/Gemfile b/Gemfile index caccb80..ff4f5cf 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,5 @@ source 'https://rubygems.org' gem 'cocoapods', '~> 1.13' gem 'fastlane', '~> 2.216' gem 'jazzy', '~> 0.14' -gem 'rouge', '>= 2.0.7' \ No newline at end of file +gem 'rouge', '>= 2.0.7' +gem 'slather', '~> 2.0' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index eec4c98..bae18f4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,6 +33,7 @@ GEM aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) + clamp (1.3.2) cocoapods (1.13.0) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) @@ -227,11 +228,14 @@ GEM nap (1.1.0) naturally (2.2.1) netrc (0.11.0) + nokogiri (1.15.4-x86_64-darwin) + racc (~> 1.4) open4 (1.3.4) optparse (0.1.1) os (1.1.4) plist (3.7.0) public_suffix (4.0.7) + racc (1.7.1) rake (13.0.6) redcarpet (3.6.0) representable (3.2.0) @@ -255,6 +259,12 @@ GEM simctl (1.6.10) CFPropertyList naturally + slather (2.7.5) + CFPropertyList (>= 2.2, < 4) + activesupport + clamp (~> 1.3) + nokogiri (>= 1.14.3) + xcodeproj (~> 1.21) sqlite3 (1.6.6-x86_64-darwin) terminal-notifier (2.0.0) terminal-table (3.0.2) @@ -297,6 +307,7 @@ DEPENDENCIES fastlane (~> 2.216) jazzy (~> 0.14) rouge (>= 2.0.7) + slather (~> 2.0) BUNDLED WITH 2.2.22 diff --git a/Overseer b/Overseer new file mode 160000 index 0000000..9658664 --- /dev/null +++ b/Overseer @@ -0,0 +1 @@ +Subproject commit 965866421b204966bbabcbf534989dd6c12e05ba diff --git a/Romita.xcodeproj/project.pbxproj b/Romita.xcodeproj/project.pbxproj index 6ecaacd..2e3c6a0 100644 --- a/Romita.xcodeproj/project.pbxproj +++ b/Romita.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 79B4E00A2ADBCAD50073D2B0 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79B4E0052ADBCAD50073D2B0 /* UICollectionView+Extensions.swift */; }; 79B4E00B2ADBCAD50073D2B0 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79B4E0062ADBCAD50073D2B0 /* UIView+Extensions.swift */; }; 79B4E00C2ADBCAD50073D2B0 /* UIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79B4E0072ADBCAD50073D2B0 /* UIApplication+Extensions.swift */; }; + 79F8EB5F2AE1B10B00F438BF /* UIControl+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F8EB5E2AE1B10B00F438BF /* UIControl+Extensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -120,6 +121,7 @@ 79B4E0052ADBCAD50073D2B0 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; 79B4E0062ADBCAD50073D2B0 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 79B4E0072ADBCAD50073D2B0 /* UIApplication+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extensions.swift"; sourceTree = ""; }; + 79F8EB5E2AE1B10B00F438BF /* UIControl+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -193,6 +195,7 @@ 79B4E0012ADBB3C80073D2B0 /* ROMView+Extensions.swift */, 79B4E0072ADBCAD50073D2B0 /* UIApplication+Extensions.swift */, 79B4E0052ADBCAD50073D2B0 /* UICollectionView+Extensions.swift */, + 79F8EB5E2AE1B10B00F438BF /* UIControl+Extensions.swift */, 79B4E0032ADBCAD40073D2B0 /* UIStackView+Extensions.swift */, 79B4E0042ADBCAD50073D2B0 /* UITableView+Extensions.swift */, 79B4E0062ADBCAD50073D2B0 /* UIView+Extensions.swift */, @@ -432,6 +435,7 @@ 79B4E00B2ADBCAD50073D2B0 /* UIView+Extensions.swift in Sources */, 79B4DFDF2ADB8FDC0073D2B0 /* ROMColor+Extensions.swift in Sources */, 79B4E00A2ADBCAD50073D2B0 /* UICollectionView+Extensions.swift in Sources */, + 79F8EB5F2AE1B10B00F438BF /* UIControl+Extensions.swift in Sources */, 79B4E0082ADBCAD50073D2B0 /* UIStackView+Extensions.swift in Sources */, 796DF6292AD236B200A18A4C /* Types.swift in Sources */, 79B4E0022ADBB3C80073D2B0 /* ROMView+Extensions.swift in Sources */, diff --git a/Romita.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Romita.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9ddc0cd..c5f22cb 100644 --- a/Romita.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Romita.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Kosoku/Feige", "state" : { - "revision" : "7934b25bf18bc65e0cca0240a8bb8ef1858d4312", - "version" : "2.0.1" + "revision" : "ee0d2d3dd48454c2824db24bac7dcc527d6855d2", + "version" : "2.2.0" } } ], diff --git a/Romita/UIControl+Extensions.swift b/Romita/UIControl+Extensions.swift new file mode 100644 index 0000000..873ebed --- /dev/null +++ b/Romita/UIControl+Extensions.swift @@ -0,0 +1,121 @@ +// +// UIControl+Extensions.swift +// Romita +// +// Created by William Towe on 10/19/23. +// Copyright © 2023 Kosoku Interactive, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +#if os(iOS) || os(tvOS) +import UIKit + +private final class UIControlBlockWrapper: NSObject { + // MARK: - Public Properties + let block: UIControl.Block + weak var control: UIControl? + let controlEvents: UIControl.Event + + // MARK: - Private Functions + @objc + private func controlAction(sender: UIControl) { + self.block(sender, self.controlEvents) + } + + // MARK: - Initializers + init(block: @escaping UIControl.Block, control: UIControl, controlEvents: UIControl.Event) { + self.block = block + self.control = control + self.controlEvents = controlEvents + + super.init() + + control.addTarget(self, action: #selector(controlAction(sender:)), for: self.controlEvents) + } + + deinit { + self.control?.removeTarget(self, action: #selector(controlAction(sender:)), for: self.controlEvents) + } +} + +extension UIControl.Event: Hashable { + // MARK: - Hashable + public func hash(into hasher: inout Hasher) { + hasher.combine(self.rawValue) + } +} + +public extension UIControl { + // MARK: - Public Types + /** + Block to invoke when control events are triggered. + */ + typealias Block = (UIControl, UIControl.Event) -> Void + + // MARK: - Private Properties + private var controlEventsToControlBlockWrappers: [UIControl.Event: Set] { + get { + (objc_getAssociatedObject(self, #function) as? [UIControl.Event: Set]).valueOrEmpty + } + set { + objc_setAssociatedObject(self, #function, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + // MARK: - Public Functions + /** + Returns whether the control has blocks associated with it for the provided `controlEvents`. + + - Parameter controlEvents: The control events to check for associated blocks + - Returns: `true` if there are associated blocks, otherwise `false` + */ + func hasBlocks(forControlEvents controlEvents: UIControl.Event) -> Bool { + self.controlEventsToControlBlockWrappers[controlEvents].isNotNil + } + + /** + Add a `block` to be invoked when the provided `controlEvents` are triggered. + + - Parameter controlEvents: The control events that should invoke `block` + - Parameter block: The block to invoke when `controlEvents` are triggered + */ + func addBlock(forControlEvents controlEvents: UIControl.Event = .touchUpInside, block: @escaping Block) { + self.addControlBlockWrapper(.init(block: block, control: self, controlEvents: controlEvents)) + } + + /** + Remove all blocks for the provided `controlEvents`. + + - Parameter controlEvents: The control events for which all blocks should be removed + */ + func removeBlocks(forControlEvents controlEvents: UIControl.Event) { + var wrappers = self.controlEventsToControlBlockWrappers + + wrappers[controlEvents] = nil + + self.controlEventsToControlBlockWrappers = wrappers + } + + // MARK: - Private Functions + private func addControlBlockWrapper(_ wrapper: UIControlBlockWrapper) { + var dict = self.controlEventsToControlBlockWrappers + var wrappers = dict[wrapper.controlEvents].valueOrEmpty + + wrappers.insert(wrapper) + dict[wrapper.controlEvents] = wrappers + + self.controlEventsToControlBlockWrappers = dict + } +} +#endif diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..4282947 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,6 @@ +# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app +# apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username + + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..4eab179 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,26 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +$module_scheme_name = "Romita" + +ENV["SCHEME_NAME"] = $module_scheme_name +ENV["MODULE_NAME"] = $module_scheme_name +ENV["INFO_PLIST_PATH"] = "Romita/Info.plist" +ENV["PODSPEC_PATH"] = "Romita.podspec" +ENV["XCODEPROJ_PATH"] = "Romita.xcodeproj" + +import("../Overseer/fastlane/Fastfile") + +default_platform(:ios) diff --git a/scripts/jazzy.sh b/scripts/jazzy.sh deleted file mode 100755 index 05333b3..0000000 --- a/scripts/jazzy.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -bundle exec jazzy --clean --author Kosoku --author_url http://kosoku.com/ --source-host github --source-host-url https://github.com/Kosoku/Romita --module-version 1.1.0 --build-tool-arguments -scheme,Romita --module Romita --root-url https://kosoku.github.io/Romita/ --output docs --theme fullwidth --skip-undocumented \ No newline at end of file