From e2460ed65b74e0c3902cd2c84fbb9533713c219d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 16:44:57 -0400 Subject: [PATCH 01/89] chore(cocoapods): drop support for Carthage and refactor the project to be Cocoapods-centric --- .gitignore | 2 +- Cartfile | 2 - Cartfile.private | 2 - Cartfile.resolved | 4 - Carthage/Checkouts/Nimble | 1 - Carthage/Checkouts/Quick | 1 - Carthage/Checkouts/ReactiveCocoa | 1 - Carthage/Checkouts/ReactiveSwift | 1 - FueledUtils.playground/Contents.swift | 12 - FueledUtils.playground/contents.xcplayground | 4 - FueledUtils.podspec | 44 +- FueledUtils.xcodeproj/project.pbxproj | 1166 ----------------- .../xcschemes/FueledUtils-watchOS.xcscheme | 76 -- .../contents.xcworkspacedata | 22 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - Tests/FueledUtils.xcodeproj/project.pbxproj | 411 ++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/FueledUtils-Example.xcscheme | 68 +- .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 0 Tests/Podfile | 10 + Tests/Podfile.lock | 36 + .../Specs}/CoalescingActionSpec.swift | 0 .../Specs}/ReactiveSwiftExtensionsSpec.swift | 0 .../Supporting Files}/Info.plist | 0 _Pods.xcodeproj | 1 + 26 files changed, 535 insertions(+), 1354 deletions(-) delete mode 100644 Cartfile delete mode 100644 Cartfile.private delete mode 100644 Cartfile.resolved delete mode 160000 Carthage/Checkouts/Nimble delete mode 160000 Carthage/Checkouts/Quick delete mode 160000 Carthage/Checkouts/ReactiveCocoa delete mode 160000 Carthage/Checkouts/ReactiveSwift delete mode 100644 FueledUtils.playground/Contents.swift delete mode 100644 FueledUtils.playground/contents.xcplayground delete mode 100644 FueledUtils.xcodeproj/project.pbxproj delete mode 100644 FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-watchOS.xcscheme delete mode 100644 FueledUtils.xcworkspace/contents.xcworkspacedata delete mode 100644 FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Tests/FueledUtils.xcodeproj/project.pbxproj create mode 100644 Tests/FueledUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils.xcscheme => Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme (62%) create mode 100644 Tests/FueledUtils.xcworkspace/contents.xcworkspacedata rename {FueledUtils.xcodeproj/project.xcworkspace => Tests/FueledUtils.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 Tests/Podfile create mode 100644 Tests/Podfile.lock rename {FueledUtilsTests => Tests/Specs}/CoalescingActionSpec.swift (100%) rename {FueledUtilsTests => Tests/Specs}/ReactiveSwiftExtensionsSpec.swift (100%) rename {FueledUtilsTests => Tests/Supporting Files}/Info.plist (100%) create mode 120000 _Pods.xcodeproj diff --git a/.gitignore b/.gitignore index 2e525462..f657dea3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,7 @@ playground.xcworkspace # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # -# Pods/ +Pods/ # Carthage # diff --git a/Cartfile b/Cartfile deleted file mode 100644 index b088968f..00000000 --- a/Cartfile +++ /dev/null @@ -1,2 +0,0 @@ -github "ReactiveCocoa/ReactiveSwift" ~> 6.0 -github "ReactiveCocoa/ReactiveCocoa" ~> 10.0 diff --git a/Cartfile.private b/Cartfile.private deleted file mode 100644 index 5791b6e3..00000000 --- a/Cartfile.private +++ /dev/null @@ -1,2 +0,0 @@ -github "Quick/Quick" ~> 2.0 -github "Quick/Nimble" ~> 8.0 diff --git a/Cartfile.resolved b/Cartfile.resolved deleted file mode 100644 index 56303f04..00000000 --- a/Cartfile.resolved +++ /dev/null @@ -1,4 +0,0 @@ -github "Quick/Nimble" "v8.0.4" -github "Quick/Quick" "v2.2.0" -github "ReactiveCocoa/ReactiveCocoa" "10.1.0" -github "ReactiveCocoa/ReactiveSwift" "6.1.0" diff --git a/Carthage/Checkouts/Nimble b/Carthage/Checkouts/Nimble deleted file mode 160000 index 6abeb3f5..00000000 --- a/Carthage/Checkouts/Nimble +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6abeb3f5c03beba2b9e4dbe20886e773b5b629b6 diff --git a/Carthage/Checkouts/Quick b/Carthage/Checkouts/Quick deleted file mode 160000 index 33682c2f..00000000 --- a/Carthage/Checkouts/Quick +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 33682c2f6230c60614861dfc61df267e11a1602f diff --git a/Carthage/Checkouts/ReactiveCocoa b/Carthage/Checkouts/ReactiveCocoa deleted file mode 160000 index 21deb683..00000000 --- a/Carthage/Checkouts/ReactiveCocoa +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21deb683540db0d059dd7945627493275936b7db diff --git a/Carthage/Checkouts/ReactiveSwift b/Carthage/Checkouts/ReactiveSwift deleted file mode 160000 index b772fa0b..00000000 --- a/Carthage/Checkouts/ReactiveSwift +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b772fa0b624926e6e2f21acbb79297736a05c585 diff --git a/FueledUtils.playground/Contents.swift b/FueledUtils.playground/Contents.swift deleted file mode 100644 index 09d25acd..00000000 --- a/FueledUtils.playground/Contents.swift +++ /dev/null @@ -1,12 +0,0 @@ -import FueledUtils -import PlaygroundSupport -import UIKit - -let gradientView = GradientView(frame: CGRect(origin: .zero, size: CGSize(width: 500.0, height: 500.0))) - -gradientView.type = .radial(startCenter: CGPoint(x: 0.5, y: 0.5), startRadius: 10.0, endCenter: CGPoint(x: 0.5, y: 0.5), endRadius: 50.0) -gradientView.definition = .custom([(.black, 0.0), (.red, 0.5), (.blue, 1.0)]) - -let test: [Int] = [2, 3] -let result = test.splitBetween { $0 == 2 && $1 == 3 } -result diff --git a/FueledUtils.playground/contents.xcplayground b/FueledUtils.playground/contents.xcplayground deleted file mode 100644 index 9f5f2f40..00000000 --- a/FueledUtils.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 63812ce9..f8a20dc2 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -1,27 +1,29 @@ +# frozen_string_literal: true + Pod::Spec.new do |s| - s.name = 'FueledUtils' - s.version = '2.0.3' - s.summary = 'A collection of utilities used at Fueled' - s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' - s.swift_version = '5' + s.name = 'FueledUtils' + s.version = '2.0.3' + s.summary = 'A collection of utilities used at Fueled' + s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' + s.swift_version = '5' - s.homepage = 'https://github.com/Fueled/ios-utilities' - s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } - s.author = { 'Vadim-Yelagin' => 'vadim.yelagin@gmail.com', 'stephanecopin' => 'stephane@fueled.com', 'leontiy' => 'leonty@fueled.com', 'bastienFalcou' => 'bastien@fueled.com', 'heymansmile' => 'ivan@fueled.com', 'thib4ult' => 'thibault@fueled.com', 'notbenoit' => 'benoit@fueled.com' } - s.source = { :git => 'https://github.com/Fueled/ios-utilities.git', :tag => s.version.to_s } - s.documentation_url = 'https://cdn.rawgit.com/Fueled/ios-utilities/master/docs/index.html' + s.homepage = 'https://github.com/Fueled/ios-utilities' + s.license = { type: 'Apache License, Version 2.0', file: 'LICENSE' } + s.author = { 'Vadim-Yelagin' => 'vadim.yelagin@gmail.com', 'stephanecopin' => 'stephane@fueled.com', 'leontiy' => 'leonty@fueled.com', 'bastienFalcou' => 'bastien@fueled.com', 'heymansmile' => 'ivan@fueled.com', 'thib4ult' => 'thibault@fueled.com', 'notbenoit' => 'benoit@fueled.com' } + s.source = { git: 'https://github.com/Fueled/ios-utilities.git', tag: s.version.to_s } + s.documentation_url = 'https://cdn.rawgit.com/Fueled/ios-utilities/master/docs/index.html' - s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.9' - s.watchos.deployment_target = '2.0' - s.tvos.deployment_target = '9.0' + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.9' + s.watchos.deployment_target = '2.0' + s.tvos.deployment_target = '9.0' - s.source_files = 'FueledUtils/**/*.swift' - s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] - s.ios.exclude_files = ['FueledUtils/FueledUtils.h'] - s.watchos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] - s.tvos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/KeyboardInsetHelper.swift'] + s.source_files = 'FueledUtils/**/*.swift' + s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] + s.ios.exclude_files = ['FueledUtils/FueledUtils.h'] + s.watchos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] + s.tvos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/KeyboardInsetHelper.swift'] - s.dependency 'ReactiveSwift', '~> 6.0' - s.dependency 'ReactiveCocoa', '~> 10.0' + s.dependency 'ReactiveSwift', '~> 6.0' + s.dependency 'ReactiveCocoa', '~> 10.0' end diff --git a/FueledUtils.xcodeproj/project.pbxproj b/FueledUtils.xcodeproj/project.pbxproj deleted file mode 100644 index 1c766aa7..00000000 --- a/FueledUtils.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1166 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 0238E9351E69B53200BF26D4 /* ScrollViewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0238E9341E69B53200BF26D4 /* ScrollViewPage.swift */; }; - 024E88731DA1597900826334 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 024E88721DA1597900826334 /* ReactiveSwift.framework */; }; - 02DE3C221D258C79002B58E2 /* FueledUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 02DE3C211D258C79002B58E2 /* FueledUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 02DE3C2A1D258E02002B58E2 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DE3C291D258E02002B58E2 /* ReactiveCocoa.framework */; }; - 02DE3C3C1D258FD1002B58E2 /* ReactiveSwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */; }; - 02DE3C3E1D258FD1002B58E2 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */; }; - 02DE3C3F1D258FD1002B58E2 /* SignalingAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C381D258FD1002B58E2 /* SignalingAlert.swift */; }; - 02DE3C421D258FD1002B58E2 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */; }; - 02DE3C441D25928F002B58E2 /* KeyboardInsetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C431D25928F002B58E2 /* KeyboardInsetHelper.swift */; }; - 02DE3C461D25933E002B58E2 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C451D25933E002B58E2 /* HairlineView.swift */; }; - 02DE3C491D25952A002B58E2 /* NSDecimalNumberOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */; }; - 02DE3C4A1D25952A002B58E2 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C481D25952A002B58E2 /* Regex.swift */; }; - 02DE3C4D1D259628002B58E2 /* ButtonWithTitleAdjustment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4B1D259628002B58E2 /* ButtonWithTitleAdjustment.swift */; }; - 02DE3C4E1D259628002B58E2 /* LabelWithTitleAdjustment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4C1D259628002B58E2 /* LabelWithTitleAdjustment.swift */; }; - 02DE3C501D259966002B58E2 /* SetRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4F1D259966002B58E2 /* SetRootViewController.swift */; }; - 02DE3C551D259FA9002B58E2 /* DimmingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C541D259FA9002B58E2 /* DimmingButton.swift */; }; - 02DE3C571D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C561D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift */; }; - 02DF8ECB1E6464AB009AB29C /* ReactiveLifetimeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */; }; - 02E626091D340F0C0041E512 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E626081D340F0C0041E512 /* LoadingState.swift */; }; - 35996E0F1F55C3E1004D6AC0 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */; }; - 991267652257405F00D39A08 /* ReactiveCocoaExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574121F909290006FB3F /* ReactiveCocoaExtensions.swift */; }; - 99126766225740DB00D39A08 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3A1D258FD1002B58E2 /* UIExtensions.swift */; }; - DFA57F0621E7A64200467647 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA57F0521E7A64200467647 /* GradientView.swift */; }; - F40C574221F9092A0006FB3F /* ReactiveCocoaExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574121F909290006FB3F /* ReactiveCocoaExtensions.swift */; }; - F40C574421F909A60006FB3F /* ActionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574321F909A60006FB3F /* ActionProtocol.swift */; }; - F40C574621F90BD10006FB3F /* TypedSerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */; }; - F40C574821F9182B0006FB3F /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574721F9182B0006FB3F /* TransferState.swift */; }; - F45268392355E83200DA9B9B /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F45268382355E83200DA9B9B /* CoalescingActionSpec.swift */; }; - F463DFC1222D83060031AAA5 /* ActionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574321F909A60006FB3F /* ActionProtocol.swift */; }; - F463DFC2222D83060031AAA5 /* ButtonWithTitleAdjustment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4B1D259628002B58E2 /* ButtonWithTitleAdjustment.swift */; }; - F463DFC3222D83060031AAA5 /* InputCoalescingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */; }; - F463DFC4222D83060031AAA5 /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A619541F47263100777BB2 /* CollectionExtensions.swift */; }; - F463DFC5222D83060031AAA5 /* DecoratingTextFieldDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C561D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift */; }; - F463DFC6222D83060031AAA5 /* DimmingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C541D259FA9002B58E2 /* DimmingButton.swift */; }; - F463DFC7222D83060031AAA5 /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574721F9182B0006FB3F /* TransferState.swift */; }; - F463DFC8222D83060031AAA5 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */; }; - F463DFC9222D83060031AAA5 /* HairlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C451D25933E002B58E2 /* HairlineView.swift */; }; - F463DFCB222D83060031AAA5 /* LabelWithTitleAdjustment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4C1D259628002B58E2 /* LabelWithTitleAdjustment.swift */; }; - F463DFCC222D83060031AAA5 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E626081D340F0C0041E512 /* LoadingState.swift */; }; - F463DFCD222D83060031AAA5 /* NSDecimalNumberOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */; }; - F463DFCF222D83060031AAA5 /* ReactiveLifetimeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */; }; - F463DFD0222D83060031AAA5 /* ReactiveSwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */; }; - F463DFD1222D83060031AAA5 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C481D25952A002B58E2 /* Regex.swift */; }; - F463DFD2222D83060031AAA5 /* ScrollViewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0238E9341E69B53200BF26D4 /* ScrollViewPage.swift */; }; - F463DFD3222D83060031AAA5 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */; }; - F463DFD4222D83060031AAA5 /* SetRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C4F1D259966002B58E2 /* SetRootViewController.swift */; }; - F463DFD5222D83060031AAA5 /* SignalingAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C381D258FD1002B58E2 /* SignalingAlert.swift */; }; - F463DFD6222D83060031AAA5 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */; }; - F463DFD7222D83060031AAA5 /* TypedSerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */; }; - F463DFD9222D83060031AAA5 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA57F0521E7A64200467647 /* GradientView.swift */; }; - F463DFDA222D83060031AAA5 /* ActionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574321F909A60006FB3F /* ActionProtocol.swift */; }; - F463DFDC222D83060031AAA5 /* InputCoalescingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */; }; - F463DFDD222D83060031AAA5 /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A619541F47263100777BB2 /* CollectionExtensions.swift */; }; - F463DFE0222D83060031AAA5 /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574721F9182B0006FB3F /* TransferState.swift */; }; - F463DFE1222D83060031AAA5 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */; }; - F463DFE5222D83060031AAA5 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E626081D340F0C0041E512 /* LoadingState.swift */; }; - F463DFE6222D83060031AAA5 /* NSDecimalNumberOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */; }; - F463DFE8222D83060031AAA5 /* ReactiveLifetimeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */; }; - F463DFE9222D83060031AAA5 /* ReactiveSwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */; }; - F463DFEA222D83060031AAA5 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C481D25952A002B58E2 /* Regex.swift */; }; - F463DFEC222D83060031AAA5 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */; }; - F463DFEF222D83060031AAA5 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */; }; - F463DFF0222D83060031AAA5 /* TypedSerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */; }; - F463DFF3222D83070031AAA5 /* ActionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574321F909A60006FB3F /* ActionProtocol.swift */; }; - F463DFF5222D83070031AAA5 /* InputCoalescingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */; }; - F463DFF6222D83070031AAA5 /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A619541F47263100777BB2 /* CollectionExtensions.swift */; }; - F463DFF9222D83070031AAA5 /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574721F9182B0006FB3F /* TransferState.swift */; }; - F463DFFA222D83070031AAA5 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */; }; - F463DFFE222D83070031AAA5 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E626081D340F0C0041E512 /* LoadingState.swift */; }; - F463DFFF222D83070031AAA5 /* NSDecimalNumberOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */; }; - F463E002222D83070031AAA5 /* ReactiveSwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */; }; - F463E003222D83070031AAA5 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C481D25952A002B58E2 /* Regex.swift */; }; - F463E005222D83070031AAA5 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */; }; - F463E008222D83070031AAA5 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */; }; - F463E009222D83070031AAA5 /* TypedSerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */; }; - F463E00D222D836E0031AAA5 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E00C222D836E0031AAA5 /* ReactiveCocoa.framework */; }; - F463E00F222D836E0031AAA5 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E00E222D836E0031AAA5 /* ReactiveSwift.framework */; }; - F463E013222D837D0031AAA5 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E012222D837D0031AAA5 /* ReactiveCocoa.framework */; }; - F463E017222D837D0031AAA5 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E016222D837D0031AAA5 /* ReactiveSwift.framework */; }; - F463E019222D83890031AAA5 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E018222D83890031AAA5 /* ReactiveCocoa.framework */; }; - F463E01B222D83890031AAA5 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F463E01A222D83890031AAA5 /* ReactiveSwift.framework */; }; - F463E020222D83C40031AAA5 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE3C3A1D258FD1002B58E2 /* UIExtensions.swift */; }; - F463E022222D84620031AAA5 /* ReactiveLifetimeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */; }; - F464775D21FA3FA5005F8B7E /* InputCoalescingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */; }; - F4A619551F47263100777BB2 /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A619541F47263100777BB2 /* CollectionExtensions.swift */; }; - F4CBDF182200A72400DF24DD /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CBDF172200A72400DF24DD /* ReactiveSwiftExtensionsSpec.swift */; }; - F4CBDF1E2200A99A00DF24DD /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CBDF1D2200A99A00DF24DD /* Nimble.framework */; }; - F4CBDF202200A99A00DF24DD /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CBDF1F2200A99A00DF24DD /* Quick.framework */; }; - F4CBDF222200A99A00DF24DD /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CBDF212200A99A00DF24DD /* ReactiveCocoa.framework */; }; - F4CBDF242200A99A00DF24DD /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4CBDF232200A99A00DF24DD /* ReactiveSwift.framework */; }; - F4CBDF272200A99A00DF24DD /* FueledUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DE3C1E1D258C79002B58E2 /* FueledUtils.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 0238E9341E69B53200BF26D4 /* ScrollViewPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewPage.swift; sourceTree = ""; }; - 024E88721DA1597900826334 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "Carthage/Checkouts/ReactiveSwift/build/Debug-iphoneos/ReactiveSwift.framework"; sourceTree = ""; }; - 02DE3C1E1D258C79002B58E2 /* FueledUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FueledUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 02DE3C211D258C79002B58E2 /* FueledUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FueledUtils.h; sourceTree = ""; }; - 02DE3C231D258C79002B58E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 02DE3C291D258E02002B58E2 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensions.swift; sourceTree = ""; }; - 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceExtensions.swift; sourceTree = ""; }; - 02DE3C381D258FD1002B58E2 /* SignalingAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalingAlert.swift; sourceTree = ""; }; - 02DE3C3A1D258FD1002B58E2 /* UIExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = ""; }; - 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; - 02DE3C431D25928F002B58E2 /* KeyboardInsetHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInsetHelper.swift; sourceTree = ""; }; - 02DE3C451D25933E002B58E2 /* HairlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HairlineView.swift; sourceTree = ""; }; - 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDecimalNumberOperators.swift; sourceTree = ""; }; - 02DE3C481D25952A002B58E2 /* Regex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = ""; }; - 02DE3C4B1D259628002B58E2 /* ButtonWithTitleAdjustment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonWithTitleAdjustment.swift; sourceTree = ""; }; - 02DE3C4C1D259628002B58E2 /* LabelWithTitleAdjustment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelWithTitleAdjustment.swift; sourceTree = ""; }; - 02DE3C4F1D259966002B58E2 /* SetRootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetRootViewController.swift; sourceTree = ""; }; - 02DE3C541D259FA9002B58E2 /* DimmingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimmingButton.swift; sourceTree = ""; }; - 02DE3C561D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecoratingTextFieldDelegate.swift; sourceTree = ""; }; - 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveLifetimeProvider.swift; sourceTree = ""; }; - 02E626081D340F0C0041E512 /* LoadingState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = ""; }; - 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = ""; }; - DFA57F0521E7A64200467647 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; - F40C574121F909290006FB3F /* ReactiveCocoaExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCocoaExtensions.swift; sourceTree = ""; }; - F40C574321F909A60006FB3F /* ActionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionProtocol.swift; sourceTree = ""; }; - F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedSerialDisposable.swift; sourceTree = ""; }; - F40C574721F9182B0006FB3F /* TransferState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferState.swift; sourceTree = ""; }; - F45268382355E83200DA9B9B /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; - F463DF9F222D81D50031AAA5 /* FueledUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FueledUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463DFAC222D81E10031AAA5 /* FueledUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FueledUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463DFB9222D81F10031AAA5 /* FueledUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FueledUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E00C222D836E0031AAA5 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E00E222D836E0031AAA5 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E012222D837D0031AAA5 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E014222D837D0031AAA5 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveMapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E016222D837D0031AAA5 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E018222D83890031AAA5 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F463E01A222D83890031AAA5 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputCoalescingAction.swift; sourceTree = ""; }; - F4A619541F47263100777BB2 /* CollectionExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionExtensions.swift; sourceTree = ""; }; - F4CBDF152200A72400DF24DD /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F4CBDF172200A72400DF24DD /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; - F4CBDF192200A72400DF24DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F4CBDF1D2200A99A00DF24DD /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F4CBDF1F2200A99A00DF24DD /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F4CBDF212200A99A00DF24DD /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F4CBDF232200A99A00DF24DD /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 02DE3C1A1D258C79002B58E2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 02DE3C2A1D258E02002B58E2 /* ReactiveCocoa.framework in Frameworks */, - 024E88731DA1597900826334 /* ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DF9C222D81D50031AAA5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F463E019222D83890031AAA5 /* ReactiveCocoa.framework in Frameworks */, - F463E01B222D83890031AAA5 /* ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFA9222D81E10031AAA5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F463E013222D837D0031AAA5 /* ReactiveCocoa.framework in Frameworks */, - F463E017222D837D0031AAA5 /* ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFB6222D81F10031AAA5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F463E00D222D836E0031AAA5 /* ReactiveCocoa.framework in Frameworks */, - F463E00F222D836E0031AAA5 /* ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F4CBDF122200A72400DF24DD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F4CBDF272200A99A00DF24DD /* FueledUtils.framework in Frameworks */, - F4CBDF1E2200A99A00DF24DD /* Nimble.framework in Frameworks */, - F4CBDF202200A99A00DF24DD /* Quick.framework in Frameworks */, - F4CBDF222200A99A00DF24DD /* ReactiveCocoa.framework in Frameworks */, - F4CBDF242200A99A00DF24DD /* ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 024E88711DA1597800826334 /* Frameworks */ = { - isa = PBXGroup; - children = ( - F463E018222D83890031AAA5 /* ReactiveCocoa.framework */, - F463E01A222D83890031AAA5 /* ReactiveSwift.framework */, - F463E012222D837D0031AAA5 /* ReactiveCocoa.framework */, - F463E014222D837D0031AAA5 /* ReactiveMapKit.framework */, - F463E016222D837D0031AAA5 /* ReactiveSwift.framework */, - F463E00C222D836E0031AAA5 /* ReactiveCocoa.framework */, - F463E00E222D836E0031AAA5 /* ReactiveSwift.framework */, - F4CBDF1D2200A99A00DF24DD /* Nimble.framework */, - F4CBDF1F2200A99A00DF24DD /* Quick.framework */, - F4CBDF212200A99A00DF24DD /* ReactiveCocoa.framework */, - F4CBDF232200A99A00DF24DD /* ReactiveSwift.framework */, - 024E88721DA1597900826334 /* ReactiveSwift.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 02DE3C141D258C79002B58E2 = { - isa = PBXGroup; - children = ( - 02DE3C201D258C79002B58E2 /* FueledUtils */, - F4CBDF162200A72400DF24DD /* FueledUtilsTests */, - 02DE3C1F1D258C79002B58E2 /* Products */, - 02DE3C291D258E02002B58E2 /* ReactiveCocoa.framework */, - 024E88711DA1597800826334 /* Frameworks */, - ); - sourceTree = ""; - }; - 02DE3C1F1D258C79002B58E2 /* Products */ = { - isa = PBXGroup; - children = ( - 02DE3C1E1D258C79002B58E2 /* FueledUtils.framework */, - F4CBDF152200A72400DF24DD /* FueledUtilsTests.xctest */, - F463DF9F222D81D50031AAA5 /* FueledUtils.framework */, - F463DFAC222D81E10031AAA5 /* FueledUtils.framework */, - F463DFB9222D81F10031AAA5 /* FueledUtils.framework */, - ); - name = Products; - sourceTree = ""; - }; - 02DE3C201D258C79002B58E2 /* FueledUtils */ = { - isa = PBXGroup; - children = ( - 02DE3C211D258C79002B58E2 /* FueledUtils.h */, - 02DE3C231D258C79002B58E2 /* Info.plist */, - F40C574321F909A60006FB3F /* ActionProtocol.swift */, - 02DE3C4B1D259628002B58E2 /* ButtonWithTitleAdjustment.swift */, - F464775C21FA3FA5005F8B7E /* InputCoalescingAction.swift */, - F4A619541F47263100777BB2 /* CollectionExtensions.swift */, - 02DE3C561D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift */, - 02DE3C541D259FA9002B58E2 /* DimmingButton.swift */, - F40C574721F9182B0006FB3F /* TransferState.swift */, - 35996E0E1F55C3E1004D6AC0 /* FoundationExtensions.swift */, - 02DE3C451D25933E002B58E2 /* HairlineView.swift */, - 02DE3C431D25928F002B58E2 /* KeyboardInsetHelper.swift */, - 02DE3C4C1D259628002B58E2 /* LabelWithTitleAdjustment.swift */, - 02E626081D340F0C0041E512 /* LoadingState.swift */, - 02DE3C471D25952A002B58E2 /* NSDecimalNumberOperators.swift */, - F40C574121F909290006FB3F /* ReactiveCocoaExtensions.swift */, - 02DF8ECA1E6464AB009AB29C /* ReactiveLifetimeProvider.swift */, - 02DE3C351D258FD1002B58E2 /* ReactiveSwiftExtensions.swift */, - 02DE3C481D25952A002B58E2 /* Regex.swift */, - 0238E9341E69B53200BF26D4 /* ScrollViewPage.swift */, - 02DE3C371D258FD1002B58E2 /* SequenceExtensions.swift */, - 02DE3C4F1D259966002B58E2 /* SetRootViewController.swift */, - 02DE3C381D258FD1002B58E2 /* SignalingAlert.swift */, - 02DE3C3B1D258FD1002B58E2 /* StringExtensions.swift */, - F40C574521F90BD10006FB3F /* TypedSerialDisposable.swift */, - 02DE3C3A1D258FD1002B58E2 /* UIExtensions.swift */, - DFA57F0521E7A64200467647 /* GradientView.swift */, - ); - path = FueledUtils; - sourceTree = ""; - }; - F4CBDF162200A72400DF24DD /* FueledUtilsTests */ = { - isa = PBXGroup; - children = ( - F45268382355E83200DA9B9B /* CoalescingActionSpec.swift */, - F4CBDF172200A72400DF24DD /* ReactiveSwiftExtensionsSpec.swift */, - F4CBDF192200A72400DF24DD /* Info.plist */, - ); - path = FueledUtilsTests; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 02DE3C1B1D258C79002B58E2 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 02DE3C221D258C79002B58E2 /* FueledUtils.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DF9A222D81D50031AAA5 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFA7222D81E10031AAA5 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFB4222D81F10031AAA5 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 02DE3C1D1D258C79002B58E2 /* FueledUtils-iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 02DE3C261D258C79002B58E2 /* Build configuration list for PBXNativeTarget "FueledUtils-iOS" */; - buildPhases = ( - 35A83B012267C86100306B26 /* swiftlint */, - 02DE3C191D258C79002B58E2 /* Sources */, - 02DE3C1A1D258C79002B58E2 /* Frameworks */, - 02DE3C1B1D258C79002B58E2 /* Headers */, - 02DE3C1C1D258C79002B58E2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "FueledUtils-iOS"; - productName = FueledUtils; - productReference = 02DE3C1E1D258C79002B58E2 /* FueledUtils.framework */; - productType = "com.apple.product-type.framework"; - }; - F463DF9E222D81D50031AAA5 /* FueledUtils-watchOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = F463DFA4222D81D50031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-watchOS" */; - buildPhases = ( - 35C495542267E3B000CFC908 /* swiftlint */, - F463DF9A222D81D50031AAA5 /* Headers */, - F463DF9B222D81D50031AAA5 /* Sources */, - F463DF9C222D81D50031AAA5 /* Frameworks */, - F463DF9D222D81D50031AAA5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "FueledUtils-watchOS"; - productName = "FueledUtils-watchOS"; - productReference = F463DF9F222D81D50031AAA5 /* FueledUtils.framework */; - productType = "com.apple.product-type.framework"; - }; - F463DFAB222D81E10031AAA5 /* FueledUtils-tvOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = F463DFB1222D81E10031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-tvOS" */; - buildPhases = ( - 35C495532267E39500CFC908 /* swiftlint */, - F463DFA7222D81E10031AAA5 /* Headers */, - F463DFA8222D81E10031AAA5 /* Sources */, - F463DFA9222D81E10031AAA5 /* Frameworks */, - F463DFAA222D81E10031AAA5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "FueledUtils-tvOS"; - productName = "FueledUtils-tvOS"; - productReference = F463DFAC222D81E10031AAA5 /* FueledUtils.framework */; - productType = "com.apple.product-type.framework"; - }; - F463DFB8222D81F10031AAA5 /* FueledUtils-macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = F463DFBE222D81F10031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-macOS" */; - buildPhases = ( - 35C495522267E37800CFC908 /* swiftlint */, - F463DFB4222D81F10031AAA5 /* Headers */, - F463DFB5222D81F10031AAA5 /* Sources */, - F463DFB6222D81F10031AAA5 /* Frameworks */, - F463DFB7222D81F10031AAA5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "FueledUtils-macOS"; - productName = "FueledUtils-macOS"; - productReference = F463DFB9222D81F10031AAA5 /* FueledUtils.framework */; - productType = "com.apple.product-type.framework"; - }; - F4CBDF142200A72400DF24DD /* FueledUtilsTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = F4CBDF1C2200A72400DF24DD /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */; - buildPhases = ( - F4CBDF112200A72400DF24DD /* Sources */, - F4CBDF122200A72400DF24DD /* Frameworks */, - F4CBDF132200A72400DF24DD /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = FueledUtilsTests; - productName = FueledUtilsTest; - productReference = F4CBDF152200A72400DF24DD /* FueledUtilsTests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 02DE3C151D258C79002B58E2 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1110; - ORGANIZATIONNAME = Fueled; - TargetAttributes = { - 02DE3C1D1D258C79002B58E2 = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1020; - }; - F463DF9E222D81D50031AAA5 = { - CreatedOnToolsVersion = 10.1; - ProvisioningStyle = Automatic; - }; - F463DFAB222D81E10031AAA5 = { - CreatedOnToolsVersion = 10.1; - ProvisioningStyle = Automatic; - }; - F463DFB8222D81F10031AAA5 = { - CreatedOnToolsVersion = 10.1; - ProvisioningStyle = Automatic; - }; - F4CBDF142200A72400DF24DD = { - CreatedOnToolsVersion = 10.1; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = 02DE3C181D258C79002B58E2 /* Build configuration list for PBXProject "FueledUtils" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 02DE3C141D258C79002B58E2; - productRefGroup = 02DE3C1F1D258C79002B58E2 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - F463DFB8222D81F10031AAA5 /* FueledUtils-macOS */, - 02DE3C1D1D258C79002B58E2 /* FueledUtils-iOS */, - F463DFAB222D81E10031AAA5 /* FueledUtils-tvOS */, - F463DF9E222D81D50031AAA5 /* FueledUtils-watchOS */, - F4CBDF142200A72400DF24DD /* FueledUtilsTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 02DE3C1C1D258C79002B58E2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DF9D222D81D50031AAA5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFAA222D81E10031AAA5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFB7222D81F10031AAA5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F4CBDF132200A72400DF24DD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 35A83B012267C86100306B26 /* swiftlint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = swiftlint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/zsh; - shellScript = "export SWIFTLINT_BIN=`whence swiftlint`\nif [[ -n $SWIFTLINT_BIN ]]\nthen\n $SWIFTLINT_BIN\nfi\n"; - showEnvVarsInLog = 0; - }; - 35C495522267E37800CFC908 /* swiftlint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = swiftlint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SWIFTLINT_BIN=`whence swiftlint`\nif [[ -n $SWIFTLINT_BIN ]]\nthen\n$SWIFTLINT_BIN\nfi\n"; - showEnvVarsInLog = 0; - }; - 35C495532267E39500CFC908 /* swiftlint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = swiftlint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SWIFTLINT_BIN=`whence swiftlint`\nif [[ -n $SWIFTLINT_BIN ]]\nthen\n$SWIFTLINT_BIN\nfi\n"; - showEnvVarsInLog = 0; - }; - 35C495542267E3B000CFC908 /* swiftlint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = swiftlint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SWIFTLINT_BIN=`whence swiftlint`\nif [[ -n $SWIFTLINT_BIN ]]\nthen\n$SWIFTLINT_BIN\nfi\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 02DE3C191D258C79002B58E2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 02DE3C4A1D25952A002B58E2 /* Regex.swift in Sources */, - 02DE3C4D1D259628002B58E2 /* ButtonWithTitleAdjustment.swift in Sources */, - DFA57F0621E7A64200467647 /* GradientView.swift in Sources */, - 02DE3C421D258FD1002B58E2 /* StringExtensions.swift in Sources */, - 02DE3C501D259966002B58E2 /* SetRootViewController.swift in Sources */, - 02DE3C441D25928F002B58E2 /* KeyboardInsetHelper.swift in Sources */, - 02DE3C3C1D258FD1002B58E2 /* ReactiveSwiftExtensions.swift in Sources */, - 02DF8ECB1E6464AB009AB29C /* ReactiveLifetimeProvider.swift in Sources */, - F463E020222D83C40031AAA5 /* UIExtensions.swift in Sources */, - 0238E9351E69B53200BF26D4 /* ScrollViewPage.swift in Sources */, - 35996E0F1F55C3E1004D6AC0 /* FoundationExtensions.swift in Sources */, - 02DE3C4E1D259628002B58E2 /* LabelWithTitleAdjustment.swift in Sources */, - 02DE3C461D25933E002B58E2 /* HairlineView.swift in Sources */, - 02DE3C571D25A24B002B58E2 /* DecoratingTextFieldDelegate.swift in Sources */, - F40C574221F9092A0006FB3F /* ReactiveCocoaExtensions.swift in Sources */, - 02DE3C491D25952A002B58E2 /* NSDecimalNumberOperators.swift in Sources */, - F4A619551F47263100777BB2 /* CollectionExtensions.swift in Sources */, - F40C574421F909A60006FB3F /* ActionProtocol.swift in Sources */, - 02E626091D340F0C0041E512 /* LoadingState.swift in Sources */, - 02DE3C3E1D258FD1002B58E2 /* SequenceExtensions.swift in Sources */, - 02DE3C3F1D258FD1002B58E2 /* SignalingAlert.swift in Sources */, - F464775D21FA3FA5005F8B7E /* InputCoalescingAction.swift in Sources */, - F40C574621F90BD10006FB3F /* TypedSerialDisposable.swift in Sources */, - 02DE3C551D259FA9002B58E2 /* DimmingButton.swift in Sources */, - F40C574821F9182B0006FB3F /* TransferState.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DF9B222D81D50031AAA5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F463DFE5222D83060031AAA5 /* LoadingState.swift in Sources */, - F463DFDA222D83060031AAA5 /* ActionProtocol.swift in Sources */, - F463DFE0222D83060031AAA5 /* TransferState.swift in Sources */, - F463DFDD222D83060031AAA5 /* CollectionExtensions.swift in Sources */, - F463DFEF222D83060031AAA5 /* StringExtensions.swift in Sources */, - F463DFDC222D83060031AAA5 /* InputCoalescingAction.swift in Sources */, - F463DFEA222D83060031AAA5 /* Regex.swift in Sources */, - F463DFE1222D83060031AAA5 /* FoundationExtensions.swift in Sources */, - F463DFE9222D83060031AAA5 /* ReactiveSwiftExtensions.swift in Sources */, - F463DFE8222D83060031AAA5 /* ReactiveLifetimeProvider.swift in Sources */, - F463DFF0222D83060031AAA5 /* TypedSerialDisposable.swift in Sources */, - F463DFE6222D83060031AAA5 /* NSDecimalNumberOperators.swift in Sources */, - F463DFEC222D83060031AAA5 /* SequenceExtensions.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFA8222D81E10031AAA5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F463DFCC222D83060031AAA5 /* LoadingState.swift in Sources */, - F463DFC1222D83060031AAA5 /* ActionProtocol.swift in Sources */, - F463DFC7222D83060031AAA5 /* TransferState.swift in Sources */, - 99126766225740DB00D39A08 /* UIExtensions.swift in Sources */, - F463DFC4222D83060031AAA5 /* CollectionExtensions.swift in Sources */, - F463DFCB222D83060031AAA5 /* LabelWithTitleAdjustment.swift in Sources */, - F463DFD4222D83060031AAA5 /* SetRootViewController.swift in Sources */, - F463DFD6222D83060031AAA5 /* StringExtensions.swift in Sources */, - F463DFC3222D83060031AAA5 /* InputCoalescingAction.swift in Sources */, - F463DFD1222D83060031AAA5 /* Regex.swift in Sources */, - F463DFC8222D83060031AAA5 /* FoundationExtensions.swift in Sources */, - F463DFC6222D83060031AAA5 /* DimmingButton.swift in Sources */, - F463DFD0222D83060031AAA5 /* ReactiveSwiftExtensions.swift in Sources */, - F463DFCF222D83060031AAA5 /* ReactiveLifetimeProvider.swift in Sources */, - 991267652257405F00D39A08 /* ReactiveCocoaExtensions.swift in Sources */, - F463DFD7222D83060031AAA5 /* TypedSerialDisposable.swift in Sources */, - F463DFC2222D83060031AAA5 /* ButtonWithTitleAdjustment.swift in Sources */, - F463DFC5222D83060031AAA5 /* DecoratingTextFieldDelegate.swift in Sources */, - F463DFCD222D83060031AAA5 /* NSDecimalNumberOperators.swift in Sources */, - F463DFD3222D83060031AAA5 /* SequenceExtensions.swift in Sources */, - F463DFD5222D83060031AAA5 /* SignalingAlert.swift in Sources */, - F463DFD9222D83060031AAA5 /* GradientView.swift in Sources */, - F463DFD2222D83060031AAA5 /* ScrollViewPage.swift in Sources */, - F463DFC9222D83060031AAA5 /* HairlineView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463DFB5222D81F10031AAA5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F463DFFE222D83070031AAA5 /* LoadingState.swift in Sources */, - F463DFF3222D83070031AAA5 /* ActionProtocol.swift in Sources */, - F463DFF9222D83070031AAA5 /* TransferState.swift in Sources */, - F463DFF6222D83070031AAA5 /* CollectionExtensions.swift in Sources */, - F463E008222D83070031AAA5 /* StringExtensions.swift in Sources */, - F463DFF5222D83070031AAA5 /* InputCoalescingAction.swift in Sources */, - F463E003222D83070031AAA5 /* Regex.swift in Sources */, - F463DFFA222D83070031AAA5 /* FoundationExtensions.swift in Sources */, - F463E022222D84620031AAA5 /* ReactiveLifetimeProvider.swift in Sources */, - F463E002222D83070031AAA5 /* ReactiveSwiftExtensions.swift in Sources */, - F463E009222D83070031AAA5 /* TypedSerialDisposable.swift in Sources */, - F463DFFF222D83070031AAA5 /* NSDecimalNumberOperators.swift in Sources */, - F463E005222D83070031AAA5 /* SequenceExtensions.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F4CBDF112200A72400DF24DD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F45268392355E83200DA9B9B /* CoalescingActionSpec.swift in Sources */, - F4CBDF182200A72400DF24DD /* ReactiveSwiftExtensionsSpec.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 02DE3C241D258C79002B58E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 11.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 4.0; - }; - name = Debug; - }; - 02DE3C251D258C79002B58E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 11.0; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 4.0; - }; - name = Release; - }; - 02DE3C271D258C79002B58E2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - PRODUCT_BUNDLE_IDENTIFIER = com.fueled.FueledUtils; - PRODUCT_NAME = FueledUtils; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 02DE3C281D258C79002B58E2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - PRODUCT_BUNDLE_IDENTIFIER = com.fueled.FueledUtils; - PRODUCT_NAME = FueledUtils; - SKIP_INSTALL = YES; - }; - name = Release; - }; - F463DFA5222D81D50031AAA5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-watchOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = watchos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - TARGETED_DEVICE_FAMILY = 4; - }; - name = Debug; - }; - F463DFA6222D81D50031AAA5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-watchOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = watchos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 4; - }; - name = Release; - }; - F463DFB2222D81E10031AAA5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-tvOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - TARGETED_DEVICE_FAMILY = 3; - }; - name = Debug; - }; - F463DFB3222D81E10031AAA5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-tvOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 3; - }; - name = Release; - }; - F463DFBF222D81F10031AAA5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-macOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - }; - name = Debug; - }; - F463DFC0222D81F10031AAA5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtils/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 2.0.3; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtils-macOS"; - PRODUCT_NAME = FueledUtils; - SDKROOT = macosx; - SKIP_INSTALL = YES; - }; - name = Release; - }; - F4CBDF1A2200A72400DF24DD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtilsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.fueled.FueledUtilsTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - F4CBDF1B2200A72400DF24DD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = FueledUtilsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.fueled.FueledUtilsTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 02DE3C181D258C79002B58E2 /* Build configuration list for PBXProject "FueledUtils" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 02DE3C241D258C79002B58E2 /* Debug */, - 02DE3C251D258C79002B58E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 02DE3C261D258C79002B58E2 /* Build configuration list for PBXNativeTarget "FueledUtils-iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 02DE3C271D258C79002B58E2 /* Debug */, - 02DE3C281D258C79002B58E2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F463DFA4222D81D50031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-watchOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F463DFA5222D81D50031AAA5 /* Debug */, - F463DFA6222D81D50031AAA5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F463DFB1222D81E10031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-tvOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F463DFB2222D81E10031AAA5 /* Debug */, - F463DFB3222D81E10031AAA5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F463DFBE222D81F10031AAA5 /* Build configuration list for PBXNativeTarget "FueledUtils-macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F463DFBF222D81F10031AAA5 /* Debug */, - F463DFC0222D81F10031AAA5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F4CBDF1C2200A72400DF24DD /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F4CBDF1A2200A72400DF24DD /* Debug */, - F4CBDF1B2200A72400DF24DD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 02DE3C151D258C79002B58E2 /* Project object */; -} diff --git a/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-watchOS.xcscheme b/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-watchOS.xcscheme deleted file mode 100644 index f6ee4297..00000000 --- a/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-watchOS.xcscheme +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FueledUtils.xcworkspace/contents.xcworkspacedata b/FueledUtils.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index cd4892b8..00000000 --- a/FueledUtils.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4b1f1eae --- /dev/null +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -0,0 +1,411 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 49C424FBE46C8C373B84165E /* Pods_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */; }; + F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; + F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.release.xcconfig"; sourceTree = ""; }; + 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.release.xcconfig"; sourceTree = ""; }; + 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; + 30FF6F506B3687CD71C7CA63 /* Pods-FueledUtils_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.debug.xcconfig"; sourceTree = ""; }; + 50C905A2E5F29D21C72DDE72 /* Pods-FueledUtils_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.release.xcconfig"; sourceTree = ""; }; + 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 68EB228E5A42939F3F5A53FA /* Pods-FueledUtils_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.debug.xcconfig"; sourceTree = ""; }; + 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtils_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; + E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; + F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; + F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACE21AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 49C424FBE46C8C373B84165E /* Pods_FueledUtilsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29F7E676D0DA9F23C4893ECA /* Pods */ = { + isa = PBXGroup; + children = ( + 68EB228E5A42939F3F5A53FA /* Pods-FueledUtils_Example.debug.xcconfig */, + 50C905A2E5F29D21C72DDE72 /* Pods-FueledUtils_Example.release.xcconfig */, + 30FF6F506B3687CD71C7CA63 /* Pods-FueledUtils_Tests.debug.xcconfig */, + 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */, + 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */, + 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 3F2C90138F935B412FFC6818 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */, + 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + F463C739241835DD000A0B29 /* Specs */, + F463C73E241835EA000A0B29 /* Supporting Files */, + 607FACD11AFB9204008FA782 /* Products */, + 29F7E676D0DA9F23C4893ECA /* Pods */, + 3F2C90138F935B412FFC6818 /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */, + E7AC5BA00D7F6054AC66E468 /* README.md */, + F7F10FE9C8384333882C2368 /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; + F463C739241835DD000A0B29 /* Specs */ = { + isa = PBXGroup; + children = ( + F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */, + F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, + ); + path = Specs; + sourceTree = ""; + }; + F463C73E241835EA000A0B29 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + F463C73F241835EA000A0B29 /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACE41AFB9204008FA782 /* FueledUtilsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */; + buildPhases = ( + 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */, + 607FACE11AFB9204008FA782 /* Sources */, + 607FACE21AFB9204008FA782 /* Frameworks */, + 607FACE31AFB9204008FA782 /* Resources */, + 39A7A643C6627B4368EAE656 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FueledUtilsTests; + productName = Tests; + productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 1140; + ORGANIZATIONNAME = CocoaPods; + TargetAttributes = { + 607FACE41AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 0900; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FueledUtils" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACE41AFB9204008FA782 /* FueledUtilsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACE31AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 39A7A643C6627B4368EAE656 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FueledUtils/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveSwift/ReactiveSwift.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveSwift.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FueledUtilsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACE11AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */, + F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF31AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 607FACF41AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = "Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FueledUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF31AFB9204008FA782 /* Debug */, + 607FACF41AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Tests/FueledUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/FueledUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..8fe1237a --- /dev/null +++ b/Tests/FueledUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme similarity index 62% rename from FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils.xcscheme rename to Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme index bd9e3879..e310125a 100644 --- a/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -26,34 +40,22 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES" - onlyGenerateCoverageForSpecifiedTargets = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + skipped = "NO"> @@ -71,15 +73,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - + diff --git a/Tests/FueledUtils.xcworkspace/contents.xcworkspacedata b/Tests/FueledUtils.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..514f9518 --- /dev/null +++ b/Tests/FueledUtils.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/FueledUtils.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from FueledUtils.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Tests/FueledUtils.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Tests/Podfile b/Tests/Podfile new file mode 100644 index 00000000..ba182e86 --- /dev/null +++ b/Tests/Podfile @@ -0,0 +1,10 @@ +platform :ios, '8.0' + +inhibit_all_warnings! +use_frameworks! + +target 'FueledUtilsTests' do + pod 'FueledUtils', path: '../' + pod 'Quick', '~> 1.2.0' + pod 'Nimble', '~> 7.0' +end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock new file mode 100644 index 00000000..a2197c70 --- /dev/null +++ b/Tests/Podfile.lock @@ -0,0 +1,36 @@ +PODS: + - FueledUtils (2.0.3): + - ReactiveCocoa (~> 10.0) + - ReactiveSwift (~> 6.0) + - Nimble (7.3.4) + - Quick (1.2.0) + - ReactiveCocoa (10.2.0): + - ReactiveSwift (~> 6.2) + - ReactiveSwift (6.2.0) + +DEPENDENCIES: + - FueledUtils (from `../`) + - Nimble (~> 7.0) + - Quick (~> 1.2.0) + +SPEC REPOS: + trunk: + - Nimble + - Quick + - ReactiveCocoa + - ReactiveSwift + +EXTERNAL SOURCES: + FueledUtils: + :path: "../" + +SPEC CHECKSUMS: + FueledUtils: 560c116894478c8cd75e393e46476677008760b9 + Nimble: 051e3d8912d40138fa5591c78594f95fb172af37 + Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 + ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 + ReactiveSwift: 3dc8800378110b13d40b46ab362afa9f6bcffc6c + +PODFILE CHECKSUM: 6df8884e9fdcb6e14d03ed551d02eafee2bc2233 + +COCOAPODS: 1.8.4 diff --git a/FueledUtilsTests/CoalescingActionSpec.swift b/Tests/Specs/CoalescingActionSpec.swift similarity index 100% rename from FueledUtilsTests/CoalescingActionSpec.swift rename to Tests/Specs/CoalescingActionSpec.swift diff --git a/FueledUtilsTests/ReactiveSwiftExtensionsSpec.swift b/Tests/Specs/ReactiveSwiftExtensionsSpec.swift similarity index 100% rename from FueledUtilsTests/ReactiveSwiftExtensionsSpec.swift rename to Tests/Specs/ReactiveSwiftExtensionsSpec.swift diff --git a/FueledUtilsTests/Info.plist b/Tests/Supporting Files/Info.plist similarity index 100% rename from FueledUtilsTests/Info.plist rename to Tests/Supporting Files/Info.plist diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj new file mode 120000 index 00000000..3c5a8e71 --- /dev/null +++ b/_Pods.xcodeproj @@ -0,0 +1 @@ +Example/Pods/Pods.xcodeproj \ No newline at end of file From 1a412a6ba58bcbcea3a10065429f1bdf883a3ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 17:06:34 -0400 Subject: [PATCH 02/89] refactor(podspec): refactor podspec to use subspecs --- FueledUtils.podspec | 21 +++++++-- FueledUtils/{ => Core}/ActionProtocol.swift | 0 .../{ => Core}/FoundationExtensions.swift | 0 .../{ => Core}/NSDecimalNumberOperators.swift | 0 FueledUtils/{ => Core}/Regex.swift | 0 .../{ => Core}/SequenceExtensions.swift | 0 FueledUtils/{ => Core}/StringExtensions.swift | 0 FueledUtils/{ => Core}/TransferState.swift | 0 .../InputCoalescingAction.swift | 0 .../{ => ReactiveSwift}/LoadingState.swift | 0 .../ReactiveCocoaExtensions.swift | 0 .../ReactiveLifetimeProvider.swift | 0 .../ReactiveSwiftExtensions.swift | 0 .../TypedSerialDisposable.swift | 0 .../SignalingAlert.swift | 0 .../ButtonWithTitleAdjustment.swift | 0 .../{ => UIKit}/CollectionExtensions.swift | 0 .../DecoratingTextFieldDelegate.swift | 0 FueledUtils/{ => UIKit}/DimmingButton.swift | 0 FueledUtils/{ => UIKit}/GradientView.swift | 0 FueledUtils/{ => UIKit}/HairlineView.swift | 0 .../{ => UIKit}/KeyboardInsetHelper.swift | 0 .../LabelWithTitleAdjustment.swift | 0 FueledUtils/{ => UIKit}/ScrollViewPage.swift | 0 .../{ => UIKit}/SetRootViewController.swift | 0 FueledUtils/{ => UIKit}/UIExtensions.swift | 0 Tests/FueledUtils.xcodeproj/project.pbxproj | 44 ++++++++++--------- .../ReactiveSwift.xcscheme} | 8 ++-- Tests/Podfile | 12 ++--- Tests/Podfile.lock | 10 +++-- .../CoalescingActionSpec.swift | 0 .../ReactiveSwiftExtensionsSpec.swift | 0 32 files changed, 58 insertions(+), 37 deletions(-) rename FueledUtils/{ => Core}/ActionProtocol.swift (100%) rename FueledUtils/{ => Core}/FoundationExtensions.swift (100%) rename FueledUtils/{ => Core}/NSDecimalNumberOperators.swift (100%) rename FueledUtils/{ => Core}/Regex.swift (100%) rename FueledUtils/{ => Core}/SequenceExtensions.swift (100%) rename FueledUtils/{ => Core}/StringExtensions.swift (100%) rename FueledUtils/{ => Core}/TransferState.swift (100%) rename FueledUtils/{ => ReactiveSwift}/InputCoalescingAction.swift (100%) rename FueledUtils/{ => ReactiveSwift}/LoadingState.swift (100%) rename FueledUtils/{ => ReactiveSwift}/ReactiveCocoaExtensions.swift (100%) rename FueledUtils/{ => ReactiveSwift}/ReactiveLifetimeProvider.swift (100%) rename FueledUtils/{ => ReactiveSwift}/ReactiveSwiftExtensions.swift (100%) rename FueledUtils/{ => ReactiveSwift}/TypedSerialDisposable.swift (100%) rename FueledUtils/{ => ReactiveSwiftUIKit}/SignalingAlert.swift (100%) rename FueledUtils/{ => UIKit}/ButtonWithTitleAdjustment.swift (100%) rename FueledUtils/{ => UIKit}/CollectionExtensions.swift (100%) rename FueledUtils/{ => UIKit}/DecoratingTextFieldDelegate.swift (100%) rename FueledUtils/{ => UIKit}/DimmingButton.swift (100%) rename FueledUtils/{ => UIKit}/GradientView.swift (100%) rename FueledUtils/{ => UIKit}/HairlineView.swift (100%) rename FueledUtils/{ => UIKit}/KeyboardInsetHelper.swift (100%) rename FueledUtils/{ => UIKit}/LabelWithTitleAdjustment.swift (100%) rename FueledUtils/{ => UIKit}/ScrollViewPage.swift (100%) rename FueledUtils/{ => UIKit}/SetRootViewController.swift (100%) rename FueledUtils/{ => UIKit}/UIExtensions.swift (100%) rename Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/{FueledUtils-Example.xcscheme => FueledUtils/ReactiveSwift.xcscheme} (93%) rename Tests/{Specs => ReactiveSwift}/CoalescingActionSpec.swift (100%) rename Tests/{Specs => ReactiveSwift}/ReactiveSwiftExtensionsSpec.swift (100%) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index f8a20dc2..5b52fd34 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -18,12 +18,27 @@ Pod::Spec.new do |s| s.watchos.deployment_target = '2.0' s.tvos.deployment_target = '9.0' - s.source_files = 'FueledUtils/**/*.swift' + s.subspec 'Core' do |s| + s.source_files = 'FueledUtils/Core/**/*.swift' + end + + s.subspec 'ReactiveSwift' do |s| + s.dependency 'FueledUtils/Core' + s.dependency 'ReactiveSwift', '~> 6.0' + s.dependency 'ReactiveCocoa', '~> 10.0' + + s.source_files = 'FueledUtils/ReactiveSwift/**/*.swift' + end + + s.subspec 'UIKit' do |s| + s.dependency 'FueledUtils/Core' + s.source_files = 'FueledUtils/UIKit/**/*.swift' + end + s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] s.ios.exclude_files = ['FueledUtils/FueledUtils.h'] s.watchos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] s.tvos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/KeyboardInsetHelper.swift'] - s.dependency 'ReactiveSwift', '~> 6.0' - s.dependency 'ReactiveCocoa', '~> 10.0' + s.default_subspecs = 'Core' end diff --git a/FueledUtils/ActionProtocol.swift b/FueledUtils/Core/ActionProtocol.swift similarity index 100% rename from FueledUtils/ActionProtocol.swift rename to FueledUtils/Core/ActionProtocol.swift diff --git a/FueledUtils/FoundationExtensions.swift b/FueledUtils/Core/FoundationExtensions.swift similarity index 100% rename from FueledUtils/FoundationExtensions.swift rename to FueledUtils/Core/FoundationExtensions.swift diff --git a/FueledUtils/NSDecimalNumberOperators.swift b/FueledUtils/Core/NSDecimalNumberOperators.swift similarity index 100% rename from FueledUtils/NSDecimalNumberOperators.swift rename to FueledUtils/Core/NSDecimalNumberOperators.swift diff --git a/FueledUtils/Regex.swift b/FueledUtils/Core/Regex.swift similarity index 100% rename from FueledUtils/Regex.swift rename to FueledUtils/Core/Regex.swift diff --git a/FueledUtils/SequenceExtensions.swift b/FueledUtils/Core/SequenceExtensions.swift similarity index 100% rename from FueledUtils/SequenceExtensions.swift rename to FueledUtils/Core/SequenceExtensions.swift diff --git a/FueledUtils/StringExtensions.swift b/FueledUtils/Core/StringExtensions.swift similarity index 100% rename from FueledUtils/StringExtensions.swift rename to FueledUtils/Core/StringExtensions.swift diff --git a/FueledUtils/TransferState.swift b/FueledUtils/Core/TransferState.swift similarity index 100% rename from FueledUtils/TransferState.swift rename to FueledUtils/Core/TransferState.swift diff --git a/FueledUtils/InputCoalescingAction.swift b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift similarity index 100% rename from FueledUtils/InputCoalescingAction.swift rename to FueledUtils/ReactiveSwift/InputCoalescingAction.swift diff --git a/FueledUtils/LoadingState.swift b/FueledUtils/ReactiveSwift/LoadingState.swift similarity index 100% rename from FueledUtils/LoadingState.swift rename to FueledUtils/ReactiveSwift/LoadingState.swift diff --git a/FueledUtils/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift similarity index 100% rename from FueledUtils/ReactiveCocoaExtensions.swift rename to FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift diff --git a/FueledUtils/ReactiveLifetimeProvider.swift b/FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift similarity index 100% rename from FueledUtils/ReactiveLifetimeProvider.swift rename to FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift diff --git a/FueledUtils/ReactiveSwiftExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift similarity index 100% rename from FueledUtils/ReactiveSwiftExtensions.swift rename to FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift diff --git a/FueledUtils/TypedSerialDisposable.swift b/FueledUtils/ReactiveSwift/TypedSerialDisposable.swift similarity index 100% rename from FueledUtils/TypedSerialDisposable.swift rename to FueledUtils/ReactiveSwift/TypedSerialDisposable.swift diff --git a/FueledUtils/SignalingAlert.swift b/FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift similarity index 100% rename from FueledUtils/SignalingAlert.swift rename to FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift diff --git a/FueledUtils/ButtonWithTitleAdjustment.swift b/FueledUtils/UIKit/ButtonWithTitleAdjustment.swift similarity index 100% rename from FueledUtils/ButtonWithTitleAdjustment.swift rename to FueledUtils/UIKit/ButtonWithTitleAdjustment.swift diff --git a/FueledUtils/CollectionExtensions.swift b/FueledUtils/UIKit/CollectionExtensions.swift similarity index 100% rename from FueledUtils/CollectionExtensions.swift rename to FueledUtils/UIKit/CollectionExtensions.swift diff --git a/FueledUtils/DecoratingTextFieldDelegate.swift b/FueledUtils/UIKit/DecoratingTextFieldDelegate.swift similarity index 100% rename from FueledUtils/DecoratingTextFieldDelegate.swift rename to FueledUtils/UIKit/DecoratingTextFieldDelegate.swift diff --git a/FueledUtils/DimmingButton.swift b/FueledUtils/UIKit/DimmingButton.swift similarity index 100% rename from FueledUtils/DimmingButton.swift rename to FueledUtils/UIKit/DimmingButton.swift diff --git a/FueledUtils/GradientView.swift b/FueledUtils/UIKit/GradientView.swift similarity index 100% rename from FueledUtils/GradientView.swift rename to FueledUtils/UIKit/GradientView.swift diff --git a/FueledUtils/HairlineView.swift b/FueledUtils/UIKit/HairlineView.swift similarity index 100% rename from FueledUtils/HairlineView.swift rename to FueledUtils/UIKit/HairlineView.swift diff --git a/FueledUtils/KeyboardInsetHelper.swift b/FueledUtils/UIKit/KeyboardInsetHelper.swift similarity index 100% rename from FueledUtils/KeyboardInsetHelper.swift rename to FueledUtils/UIKit/KeyboardInsetHelper.swift diff --git a/FueledUtils/LabelWithTitleAdjustment.swift b/FueledUtils/UIKit/LabelWithTitleAdjustment.swift similarity index 100% rename from FueledUtils/LabelWithTitleAdjustment.swift rename to FueledUtils/UIKit/LabelWithTitleAdjustment.swift diff --git a/FueledUtils/ScrollViewPage.swift b/FueledUtils/UIKit/ScrollViewPage.swift similarity index 100% rename from FueledUtils/ScrollViewPage.swift rename to FueledUtils/UIKit/ScrollViewPage.swift diff --git a/FueledUtils/SetRootViewController.swift b/FueledUtils/UIKit/SetRootViewController.swift similarity index 100% rename from FueledUtils/SetRootViewController.swift rename to FueledUtils/UIKit/SetRootViewController.swift diff --git a/FueledUtils/UIExtensions.swift b/FueledUtils/UIKit/UIExtensions.swift similarity index 100% rename from FueledUtils/UIExtensions.swift rename to FueledUtils/UIKit/UIExtensions.swift diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 4b1f1eae..21544431 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -7,27 +7,29 @@ objects = { /* Begin PBXBuildFile section */ - 49C424FBE46C8C373B84165E /* Pods_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */; }; + C9BAC68493BD9F4548E5710C /* Pods_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.release.xcconfig"; sourceTree = ""; }; 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.release.xcconfig"; sourceTree = ""; }; 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; 30FF6F506B3687CD71C7CA63 /* Pods-FueledUtils_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.debug.xcconfig"; sourceTree = ""; }; 50C905A2E5F29D21C72DDE72 /* Pods-FueledUtils_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.release.xcconfig"; sourceTree = ""; }; - 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-ReactiveSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 68EB228E5A42939F3F5A53FA /* Pods-FueledUtils_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.debug.xcconfig"; sourceTree = ""; }; 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtils_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -35,7 +37,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 49C424FBE46C8C373B84165E /* Pods_FueledUtilsTests.framework in Frameworks */, + C9BAC68493BD9F4548E5710C /* Pods_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -51,6 +53,8 @@ 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */, 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */, 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */, + 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, + 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -59,7 +63,7 @@ isa = PBXGroup; children = ( 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */, - 1E9A149E60B583ABF5255C95 /* Pods_FueledUtilsTests.framework */, + FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */, ); name = Frameworks; sourceTree = ""; @@ -68,7 +72,7 @@ isa = PBXGroup; children = ( 607FACF51AFB993E008FA782 /* Podspec Metadata */, - F463C739241835DD000A0B29 /* Specs */, + F463C739241835DD000A0B29 /* ReactiveSwift */, F463C73E241835EA000A0B29 /* Supporting Files */, 607FACD11AFB9204008FA782 /* Products */, 29F7E676D0DA9F23C4893ECA /* Pods */, @@ -79,7 +83,7 @@ 607FACD11AFB9204008FA782 /* Products */ = { isa = PBXGroup; children = ( - 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */, + 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */, ); name = Products; sourceTree = ""; @@ -94,13 +98,13 @@ name = "Podspec Metadata"; sourceTree = ""; }; - F463C739241835DD000A0B29 /* Specs */ = { + F463C739241835DD000A0B29 /* ReactiveSwift */ = { isa = PBXGroup; children = ( F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, ); - path = Specs; + path = ReactiveSwift; sourceTree = ""; }; F463C73E241835EA000A0B29 /* Supporting Files */ = { @@ -114,9 +118,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 607FACE41AFB9204008FA782 /* FueledUtilsTests */ = { + 607FACE41AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift */ = { isa = PBXNativeTarget; - buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */; buildPhases = ( 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, @@ -128,9 +132,9 @@ ); dependencies = ( ); - name = FueledUtilsTests; + name = "FueledUtilsTests-ReactiveSwift"; productName = Tests; - productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */; + productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -162,7 +166,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 607FACE41AFB9204008FA782 /* FueledUtilsTests */, + 607FACE41AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift */, ); }; /* End PBXProject section */ @@ -184,7 +188,7 @@ files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift-frameworks.sh", "${BUILT_PRODUCTS_DIR}/FueledUtils/FueledUtils.framework", "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", @@ -201,7 +205,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */ = { @@ -219,7 +223,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-FueledUtilsTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -348,7 +352,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */; + baseConfigurationReference = 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -369,7 +373,7 @@ }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */; + baseConfigurationReference = 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -396,7 +400,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */ = { + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 607FACF31AFB9204008FA782 /* Debug */, diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils/ReactiveSwift.xcscheme similarity index 93% rename from Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme rename to Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils/ReactiveSwift.xcscheme index e310125a..cc6ad7ff 100644 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils-Example.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtils/ReactiveSwift.xcscheme @@ -29,8 +29,8 @@ @@ -56,8 +56,8 @@ diff --git a/Tests/Podfile b/Tests/Podfile index ba182e86..ebfb8d57 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -1,10 +1,10 @@ -platform :ios, '8.0' - inhibit_all_warnings! use_frameworks! -target 'FueledUtilsTests' do - pod 'FueledUtils', path: '../' - pod 'Quick', '~> 1.2.0' - pod 'Nimble', '~> 7.0' +target 'FueledUtilsTests-ReactiveSwift' do + platform :ios, '8.0' + + pod 'FueledUtils/ReactiveSwift', path: '../' + pod 'Quick', '~> 1.2.0' + pod 'Nimble', '~> 7.0' end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index a2197c70..1ba57f0a 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,5 +1,7 @@ PODS: - - FueledUtils (2.0.3): + - FueledUtils/Core (2.0.3) + - FueledUtils/ReactiveSwift (2.0.3): + - FueledUtils/Core - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) - Nimble (7.3.4) @@ -9,7 +11,7 @@ PODS: - ReactiveSwift (6.2.0) DEPENDENCIES: - - FueledUtils (from `../`) + - FueledUtils/ReactiveSwift (from `../`) - Nimble (~> 7.0) - Quick (~> 1.2.0) @@ -25,12 +27,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: 560c116894478c8cd75e393e46476677008760b9 + FueledUtils: d78f55645ec7f48564411496d6ce393e222e5ec3 Nimble: 051e3d8912d40138fa5591c78594f95fb172af37 Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 ReactiveSwift: 3dc8800378110b13d40b46ab362afa9f6bcffc6c -PODFILE CHECKSUM: 6df8884e9fdcb6e14d03ed551d02eafee2bc2233 +PODFILE CHECKSUM: d59d41b7968395125eb11a800df5f1e1d9affdfc COCOAPODS: 1.8.4 diff --git a/Tests/Specs/CoalescingActionSpec.swift b/Tests/ReactiveSwift/CoalescingActionSpec.swift similarity index 100% rename from Tests/Specs/CoalescingActionSpec.swift rename to Tests/ReactiveSwift/CoalescingActionSpec.swift diff --git a/Tests/Specs/ReactiveSwiftExtensionsSpec.swift b/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift similarity index 100% rename from Tests/Specs/ReactiveSwiftExtensionsSpec.swift rename to Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift From cef9cf1997da175b3de23bad2cb968f8d49a98c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 17:37:38 -0400 Subject: [PATCH 03/89] feat(subspecs): add Combine & SwiftUI podspecs --- FueledUtils.podspec | 47 +++- FueledUtils/Combine/Action.swift | 123 +++++++++ FueledUtils/Combine/Binding+KeyPath.swift | 22 ++ FueledUtils/Combine/Combine+Operators.swift | 108 ++++++++ .../Combine/ObservableObjectExtensions.swift | 86 ++++++ .../Combine/Published+PublisherInit.swift | 41 +++ .../Combine/Publisher+CombinePrevious.swift | 37 +++ FueledUtils/Combine/PublisherExtensions.swift | 33 +++ .../Combine/PublishersExtensions.swift | 77 ++++++ FueledUtils/Core/Atomic.swift | 69 +++++ FueledUtils/Core/Lock.swift | 94 +++++++ FueledUtils/Core/OptionalProtocol.swift | 19 ++ FueledUtils/Core/OrderedSet.swift | 141 ++++++++++ FueledUtils/Core/TransferState.swift | 77 ------ .../ActionProtocol.swift | 0 .../ReactiveSwift/InputCoalescingAction.swift | 4 +- .../ReactiveCocoaExtensions.swift | 17 -- .../TransferState+Reactive.swift | 85 ++++++ .../UIReactiveExtensions.swift | 38 +++ FueledUtils/SwiftUI/ForEach+IndexInfo.swift | 61 +++++ FueledUtils/SwiftUI/FramePreferenceKey.swift | 17 ++ FueledUtils/SwiftUI/View+AnyView.swift | 15 ++ Tests/FueledUtils.xcodeproj/project.pbxproj | 254 ++++++++++++++---- .../FueledUtilsTests-ReactiveSwift.xcscheme | 52 ++++ .../FueledUtilsTests-SwiftUI.xcscheme | 52 ++++ Tests/Podfile | 20 +- Tests/Podfile.lock | 42 ++- Tests/SwiftUI/EmptySpec.swift | 20 ++ 28 files changed, 1485 insertions(+), 166 deletions(-) create mode 100644 FueledUtils/Combine/Action.swift create mode 100644 FueledUtils/Combine/Binding+KeyPath.swift create mode 100644 FueledUtils/Combine/Combine+Operators.swift create mode 100644 FueledUtils/Combine/ObservableObjectExtensions.swift create mode 100644 FueledUtils/Combine/Published+PublisherInit.swift create mode 100644 FueledUtils/Combine/Publisher+CombinePrevious.swift create mode 100644 FueledUtils/Combine/PublisherExtensions.swift create mode 100644 FueledUtils/Combine/PublishersExtensions.swift create mode 100644 FueledUtils/Core/Atomic.swift create mode 100644 FueledUtils/Core/Lock.swift create mode 100644 FueledUtils/Core/OptionalProtocol.swift create mode 100644 FueledUtils/Core/OrderedSet.swift rename FueledUtils/{Core => ReactiveSwift}/ActionProtocol.swift (100%) create mode 100644 FueledUtils/ReactiveSwift/TransferState+Reactive.swift create mode 100644 FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift create mode 100644 FueledUtils/SwiftUI/ForEach+IndexInfo.swift create mode 100644 FueledUtils/SwiftUI/FramePreferenceKey.swift create mode 100644 FueledUtils/SwiftUI/View+AnyView.swift create mode 100644 Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme create mode 100644 Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme create mode 100644 Tests/SwiftUI/EmptySpec.swift diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 5b52fd34..030ef404 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -13,17 +13,30 @@ Pod::Spec.new do |s| s.source = { git: 'https://github.com/Fueled/ios-utilities.git', tag: s.version.to_s } s.documentation_url = 'https://cdn.rawgit.com/Fueled/ios-utilities/master/docs/index.html' - s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.9' - s.watchos.deployment_target = '2.0' - s.tvos.deployment_target = '9.0' - s.subspec 'Core' do |s| s.source_files = 'FueledUtils/Core/**/*.swift' end - s.subspec 'ReactiveSwift' do |s| + s.subspec 'iOS8' do |s| + s.dependency 'FueledUtils/Core' + + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.9' + s.watchos.deployment_target = '2.0' + s.tvos.deployment_target = '9.0' + end + + s.subspec 'iOS13' do |s| s.dependency 'FueledUtils/Core' + + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + s.watchos.deployment_target = '6.0' + s.tvos.deployment_target = '13.0' + end + + s.subspec 'ReactiveSwift' do |s| + s.dependency 'FueledUtils/iOS8' s.dependency 'ReactiveSwift', '~> 6.0' s.dependency 'ReactiveCocoa', '~> 10.0' @@ -31,10 +44,30 @@ Pod::Spec.new do |s| end s.subspec 'UIKit' do |s| - s.dependency 'FueledUtils/Core' + s.dependency 'FueledUtils/iOS8' s.source_files = 'FueledUtils/UIKit/**/*.swift' end + s.subspec 'ReactiveSwiftUIKit' do |s| + s.dependency 'FueledUtils/ReactiveSwift' + s.dependency 'FueledUtils/UIKit' + + s.source_files = 'FueledUtils/ReactiveSwiftUIKit/**/*.swift' + end + + s.subspec 'Combine' do |s| + s.dependency 'FueledUtils/iOS13' + + s.source_files = 'FueledUtils/Combine/**/*.swift' + end + + s.subspec 'SwiftUI' do |s| + s.dependency 'FueledUtils/Core' + s.dependency 'FueledUtils/Combine' + + s.source_files = 'FueledUtils/SwiftUI/**/*.swift' + end + s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] s.ios.exclude_files = ['FueledUtils/FueledUtils.h'] s.watchos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift new file mode 100644 index 00000000..66c8a5a9 --- /dev/null +++ b/FueledUtils/Combine/Action.swift @@ -0,0 +1,123 @@ +// +// Action.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/16/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine +import SwiftUI + +public protocol ActionErrorProtocol { + associatedtype InnerError: Swift.Error + + var innerError: InnerError? { get } +} + +public enum ActionError: Swift.Error { + case disabled + case failure(Error) +} + +extension ActionError: ActionErrorProtocol { + public var innerError: Error? { + if case .failure(let error) = self { + return error + } + return nil + } +} + +public final class Action { + @Published public private(set) var isExecuting: Bool = false + @Published public private(set) var isEnabled: Bool = false + + public let values: AnyPublisher + public let errors: AnyPublisher + + private let execute: (Action, Input) -> AnyPublisher> + + private var cancellables = Set([]) + + public convenience init( + execute: @escaping (Input) -> ExecutePublisher + ) where ExecutePublisher.Output == Output, ExecutePublisher.Failure == Failure { + self.init(enabledIf: Just(true), execute: execute) + } + + public init( + enabledIf isEnabled: EnabledIfPublisher, + execute: @escaping (Input) -> ExecutePublisher + ) where EnabledIfPublisher.Output == Bool, + EnabledIfPublisher.Failure == Never, + ExecutePublisher.Output == Output, + ExecutePublisher.Failure == Failure { + let values = PassthroughSubject() + let errors = PassthroughSubject() + + self.values = values.eraseToAnyPublisher() + self.errors = errors.eraseToAnyPublisher() + + let isExecutingLock = DispatchSemaphore(value: 1) + self.execute = { action, input -> AnyPublisher> in + isExecutingLock.wait() + + if !action.isEnabled || action.isExecuting { + isExecutingLock.signal() + return Fail(error: .disabled) + .eraseToAnyPublisher() + } + + action.isExecuting = true + isExecutingLock.signal() + return execute(input) + .handleEvents( + receiveOutput: { value in + values.send(value) + }, + receiveCompletion: { [weak action] completion in + switch completion { + case .finished: + isExecutingLock.wait() + action?.isExecuting = false + isExecutingLock.signal() + case .failure(let error): + errors.send(error) + } + } + ) + .mapError { .failure($0) } + .eraseToAnyPublisher() + } + + self~\.isEnabled <~ Publishers.CombineLatest( + isEnabled, + self.$isExecuting + ) + .map { $0 && !$1 } + >>> self.cancellables + } + + public func apply(_ input: Input) -> AnyPublisher> { + self.execute(self, input) + } +} + +extension Action where Input == Void { + public func apply() -> AnyPublisher> { + self.apply(()) + } +} + +extension Publisher where Failure: ActionErrorProtocol { + public func unwrappingActionError() -> AnyPublisher { + self.catch { actionError -> AnyPublisher in + if let innerError = actionError.innerError { + return Fail(error: innerError).eraseToAnyPublisher() + } + return Empty(completeImmediately: false).eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/Binding+KeyPath.swift b/FueledUtils/Combine/Binding+KeyPath.swift new file mode 100644 index 00000000..87cf1305 --- /dev/null +++ b/FueledUtils/Combine/Binding+KeyPath.swift @@ -0,0 +1,22 @@ +// +// Binding+KeyPath.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/23/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +extension Binding { + public init(_ object: Type, to keyPath: ReferenceWritableKeyPath) { + self.init( + get: { + object[keyPath: keyPath] + }, + set: { + object[keyPath: keyPath] = $0 + } + ) + } +} diff --git a/FueledUtils/Combine/Combine+Operators.swift b/FueledUtils/Combine/Combine+Operators.swift new file mode 100644 index 00000000..2600c9fd --- /dev/null +++ b/FueledUtils/Combine/Combine+Operators.swift @@ -0,0 +1,108 @@ +// +// Combine+Operators.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/11/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +// swiftlint:disable generic_type_name + +precedencegroup BindingPrecedence { + associativity: right + + higherThan: AssignmentPrecedence +} + +infix operator <~: BindingPrecedence + +precedencegroup AccessPrecedence { + associativity: right + + higherThan: BindingPrecedence +} + +infix operator ~: AccessPrecedence + +precedencegroup InsertCancellablePrecedence { + associativity: left + + lowerThan: AssignmentPrecedence +} + +infix operator >>>: InsertCancellablePrecedence + +public struct ObjectKeyPathReference { + public let object: Root + public let keyPath: ReferenceWritableKeyPath +} + +public func ~ (lhs: Object, rhs: ReferenceWritableKeyPath) -> ObjectKeyPathReference { + ObjectKeyPathReference(object: lhs, keyPath: rhs) +} + +public func <~ ( + lhs: ObjectKeyPathReference, + rhs: Publisher +) -> AnyCancellable where Publisher.Output == Value, Publisher.Failure == Never { + rhs.assign(to: lhs.keyPath, withoutRetaining: lhs.object) +} + +public func <~ ( + lhs: ObservingObject, + rhs: ObservedObject +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher { + lhs.link(to: rhs) +} + +public func <~ ( + lhs: ObservingObject, + rhs: ObservedObjectCollection +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher, ObservedObjectCollection.Element: ObservableObject { + lhs.link(to: rhs) +} + +public func <~ ( + lhs: ObservingObject, + rhs: Publisher +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher, Publisher.Output: ObservableObject { + lhs.link(to: rhs) +} + +public func <~ ( + lhs: ObservingObject, + rhs: Publisher +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher, Publisher.Output: OptionalProtocol, Publisher.Output.Wrapped: ObservableObject { + lhs.link(to: rhs) +} + +public func <~ ( + lhs: ObservingObject, + rhs: Publisher +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher, Publisher.Output: Collection, Publisher.Output.Element: ObservableObject { + lhs.link(to: rhs) +} + +public func <~ ( + lhs: ObservingObject, + rhs: ReferenceWritableKeyPath +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher { + lhs.link(to: lhs[keyPath: rhs]) +} + +public func <~ ( + lhs: ObservingObject, + rhs: ReferenceWritableKeyPath +) where ObservingObject.ObjectWillChangePublisher == ObservableObjectPublisher, ObservedObjectCollection.Element: ObservableObject { + lhs.link(to: lhs[keyPath: rhs]) +} + +public func >>> (lhs: AnyCancellable, rhs: inout CancellableCollection) where CancellableCollection.Element == AnyCancellable { + lhs.store(in: &rhs) +} + +public func >>> (lhs: AnyCancellable, rhs: inout Set) { + lhs.store(in: &rhs) +} diff --git a/FueledUtils/Combine/ObservableObjectExtensions.swift b/FueledUtils/Combine/ObservableObjectExtensions.swift new file mode 100644 index 00000000..585fa40f --- /dev/null +++ b/FueledUtils/Combine/ObservableObjectExtensions.swift @@ -0,0 +1,86 @@ +// +// ObservableObject+Link.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/11/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher { + // Perform a one-way link, where the receiver will listen for changes on the object and automatically trigger its `objectWillChange` publisher + public func link(to object: Object) { + object.objectWillChange.subscribe( + AnySubscriber( + receiveValue: { [weak self] _ in + self?.objectWillChange.send() + return .unlimited + } + ) + ) + } + + public func link(to objects: ObjectCollection) where ObjectCollection.Element: ObservableObject { + objects.forEach(self.link(to:)) + } + + public func link(to publisher: Publisher) where Publisher.Output: ObservableObject { + var cancellable: AnyCancellable? + publisher.subscribe( + AnySubscriber( + receiveValue: { [weak self] object in + _ = cancellable + cancellable = object.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() } + return .unlimited + } + ) + ) + } + + public func link(to publisher: Publisher) where Publisher.Output: OptionalProtocol, Publisher.Output.Wrapped: ObservableObject { + var cancellable: AnyCancellable? + publisher.subscribe( + AnySubscriber( + receiveValue: { [weak self] object in + _ = cancellable + cancellable = object.wrapped?.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() } + return .unlimited + } + ) + ) + } + + public func link(to publisher: Publisher) where Publisher.Output == ObjectCollection, ObjectCollection.Element: ObservableObject { + var cancellables = Set() + publisher.subscribe( + AnySubscriber( + receiveValue: { [weak self] objects in + cancellables = Set() + objects.forEach { object in + object.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() } + >>> cancellables + } + return .unlimited + } + ) + ) + } +} + +extension ObservableObject { + public var objectDidChange: AnyPublisher { + // The delay of 0.0 allows the will to transform into a Did, by waiting for exactly one run loop cycle + self.objectWillChange.delay(for: 0.0, scheduler: RunLoop.current).eraseToAnyPublisher() + } + + public var publisher: AnyPublisher { + self.objectDidChange.map { _ in self }.prepend(self).eraseToAnyPublisher() + } +} + +extension Publisher where Output: Collection, Failure == Never, Output.Element: ObservableObject { + public func onAnyChanges() -> AnyPublisher<[Output.Element], Never> { + self.flatMap { Publishers.CombineLatestMany($0.map { $0.publisher }) }.eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/Published+PublisherInit.swift b/FueledUtils/Combine/Published+PublisherInit.swift new file mode 100644 index 00000000..52f7e29e --- /dev/null +++ b/FueledUtils/Combine/Published+PublisherInit.swift @@ -0,0 +1,41 @@ +// +// Published+PublisherInit.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/23/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Published { + /// This method exists as it's currently impossible to use the projectedValue of a `@Published` property without having initialize the whole object, + /// which is obviously not ideal if the projectedValue is required to initialize the whole object (basically a chicken and egg problem) + /// This resolves the issue by initializing the Published object separately from the object, and using the result + /// Non-compiling Example: + /// + /// final class TestPublished { + /// @Published private var foo: Int = 0 + /// let fooChanges: AnyPublisher + /// + /// init() { + /// self.fooChanges = AnyPublisher(self.$foo) // 'self' used in property access '$foo' before all stored properties are initialized + /// } + /// } + /// + /// Compiling Example (using below) + /// final class TestPublished { + /// @Published private var foo: Int + /// let fooChanges: AnyPublisher + /// + /// init() { + /// self.fooChanges = AnyPublisher(Published.initWithPublisher(&self._foo, initialValue: 0)) // Compiles + /// } + /// } + public static func initWithPublisher(_ property: inout Published, initialValue: Value) -> Published.Publisher { + var published = Published(initialValue: initialValue) + let publisher = published.projectedValue + property = published + return publisher + } +} diff --git a/FueledUtils/Combine/Publisher+CombinePrevious.swift b/FueledUtils/Combine/Publisher+CombinePrevious.swift new file mode 100644 index 00000000..9771ad8d --- /dev/null +++ b/FueledUtils/Combine/Publisher+CombinePrevious.swift @@ -0,0 +1,37 @@ +// +// Combine+CombinePrevious.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/18/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Publisher { + public func combinePrevious() -> AnyPublisher<(previous: Output, current: Output), Failure> { + self.combinePreviousImplementation(nil) + } + + public func combinePrevious(_ initial: Output) -> AnyPublisher<(previous: Output, current: Output), Failure> { + self.combinePreviousImplementation(initial) + } + + private func combinePreviousImplementation(_ initial: Output?) -> AnyPublisher<(previous: Output, current: Output), Failure> { + var previousValue = initial + return self + .flatMap { output -> AnyPublisher<(previous: Output, current: Output), Failure> in + defer { + previousValue = output + } + if let currentPreviousValue = previousValue { + return Just((currentPreviousValue, output)) + .setFailureType(to: Failure.self) + .eraseToAnyPublisher() + } else { + return Empty(completeImmediately: false).eraseToAnyPublisher() + } + } + .eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/PublisherExtensions.swift b/FueledUtils/Combine/PublisherExtensions.swift new file mode 100644 index 00000000..ad2b85df --- /dev/null +++ b/FueledUtils/Combine/PublisherExtensions.swift @@ -0,0 +1,33 @@ +// +// Publisher+sink.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/23/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Publisher { + public func ignoreError() -> AnyPublisher { + self.catch { _ in Empty() }.eraseToAnyPublisher() + } + + public func sink() -> AnyCancellable { + self.sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + } +} + +extension Publisher where Failure == Never { + public func assign(to keyPath: ReferenceWritableKeyPath, withoutRetaining object: Object) -> AnyCancellable { + sink { [weak object] in + object?[keyPath: keyPath] = $0 + } + } +} + +extension Publisher where Output: OptionalProtocol { + public func ignoreNil() -> AnyPublisher { + self.flatMap { ($0.wrapped.map { Just($0).eraseToAnyPublisher() } ?? Empty().eraseToAnyPublisher()).setFailureType(to: Failure.self) }.eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift new file mode 100644 index 00000000..6a3b1b24 --- /dev/null +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -0,0 +1,77 @@ +// +// PublishersExtensions.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/11/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Publishers { + public struct CombineLatestMany: Publisher where PublisherCollection.Element: Combine.Publisher { + public typealias Output = [PublisherCollection.Element.Output] + + public typealias Failure = PublisherCollection.Element.Failure + + public let publishers: PublisherCollection + + public init(_ publishers: PublisherCollection) { + self.publishers = publishers + } + + public func receive(subscriber: Subscriber) where PublisherCollection.Element.Failure == Subscriber.Failure, Subscriber.Input == Output { + if self.publishers.isEmpty { + _ = subscriber.receive([]) + subscriber.receive(completion: .finished) + return + } + + let publishers = Array(self.publishers) + let cancellables = AtomicValue( + [ + ( + cancellable: AnyCancellable?, + latestValue: PublisherCollection.Element.Output?, + hasCompleted: Bool + ), + ](repeating: (nil, nil, false), count: self.publishers.count) + ) + publishers.enumerated().forEach { index, publisher in + let cancellable = publisher.sink( + receiveCompletion: { completion in + cancellables.modify { + switch completion { + case .failure(let error): + subscriber.receive(completion: .failure(error)) + for i in $0.indices { + $0[i].cancellable = nil + } + case .finished: + $0[index].cancellable = nil + $0[index].hasCompleted = true + if $0.allSatisfy({ $0.hasCompleted }) { + subscriber.receive(completion: .finished) + } + } + } + }, + receiveValue: { value in + cancellables.modify { + $0[index].latestValue = value + let allLatestValues = $0.compactMap { $0.latestValue } + if allLatestValues.count == publishers.count { + _ = subscriber.receive(allLatestValues) + } + } + } + ) + cancellables.modify { + if !$0[index].hasCompleted { + $0[index].cancellable = cancellable + } + } + } + } + } +} diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift new file mode 100644 index 00000000..de078271 --- /dev/null +++ b/FueledUtils/Core/Atomic.swift @@ -0,0 +1,69 @@ +// +// Atomic.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/16/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Foundation + +infix operator .=: AssignmentPrecedence + +public final class AtomicValue { + private(set) var value: Value + private let lock = Lock() + + public init(_ value: Value) { + self.value = value + } + + public static func .= (atomicValue: AtomicValue, value: Value) { + atomicValue.modify { $0 = value } + } + + public func modify(_ modify: (inout Value) -> Void) { + self.lock.lock() + defer { + self.lock.unlock() + } + modify(&self.value) + } + + public func withValue(_ getter: (Value) -> Void) { + self.lock.lock() + defer { + self.lock.unlock() + } + getter(self.value) + } +} + +@propertyWrapper +public struct Atomic { + private let atomicValue: AtomicValue + + public init(_ value: Value) { + self.atomicValue = AtomicValue(value) + } + + public init(wrappedValue value: Value) { + self.init(value) + } + + public var wrappedValue: Value { + self.atomicValue.value + } + + public mutating func modify(_ modify: (inout Value) -> Void) { + self.atomicValue.modify(modify) + } + + public mutating func withValue(_ getter: (Value) -> Void) { + self.atomicValue.withValue(getter) + } + + public static func .= (atomicValue: inout Atomic, value: Value) { + atomicValue.atomicValue .= value + } +} diff --git a/FueledUtils/Core/Lock.swift b/FueledUtils/Core/Lock.swift new file mode 100644 index 00000000..1dfbfdef --- /dev/null +++ b/FueledUtils/Core/Lock.swift @@ -0,0 +1,94 @@ +// +// Lock.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/23/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Foundation + +private protocol LockImplementation { + mutating func lock() + mutating func `try`() -> Bool + mutating func unlock() +} + +@available(iOS 10.0, *) +private struct UnfairLock: LockImplementation { + private var unfairLock = os_unfair_lock_s() + + mutating func lock() { + os_unfair_lock_lock(&self.unfairLock) + } + + mutating func `try`() -> Bool { + os_unfair_lock_trylock(&self.unfairLock) + } + + mutating func unlock() { + os_unfair_lock_unlock(&self.unfairLock) + } +} + +private struct PThreadMutexLock: LockImplementation { + private var mutex = pthread_mutex_t() + + init?() { + if pthread_mutex_init(&self.mutex, nil) != 0 { + return nil + } + } + + mutating func lock() { + pthread_mutex_lock(&self.mutex) + } + + mutating func `try`() -> Bool { + pthread_mutex_trylock(&self.mutex) == 0 + } + + mutating func unlock() { + pthread_mutex_unlock(&self.mutex) + } +} + +private struct CocoaLock: LockImplementation { + private let lockImplementation = NSLock() + + mutating func lock() { + self.lockImplementation.lock() + } + + mutating func `try`() -> Bool { + self.lockImplementation.try() + } + + mutating func unlock() { + self.lockImplementation.unlock() + } +} + +final class Lock { + private var lockImplementation: LockImplementation + + init() { + if #available(iOS 10.0, *) { + self.lockImplementation = UnfairLock() + } else { + self.lockImplementation = PThreadMutexLock() ?? CocoaLock() + } + } + + func lock() { + self.lockImplementation.lock() + } + + func `try`() -> Bool { + self.lockImplementation.try() + } + + func unlock() { + self.lockImplementation.unlock() + } +} diff --git a/FueledUtils/Core/OptionalProtocol.swift b/FueledUtils/Core/OptionalProtocol.swift new file mode 100644 index 00000000..d6382a09 --- /dev/null +++ b/FueledUtils/Core/OptionalProtocol.swift @@ -0,0 +1,19 @@ +// +// OptionalProtocol.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/9/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +public protocol OptionalProtocol { + associatedtype Wrapped + + var wrapped: Wrapped? { get } +} + +extension Optional: OptionalProtocol { + public var wrapped: Wrapped? { + self + } +} diff --git a/FueledUtils/Core/OrderedSet.swift b/FueledUtils/Core/OrderedSet.swift new file mode 100644 index 00000000..dcd4d920 --- /dev/null +++ b/FueledUtils/Core/OrderedSet.swift @@ -0,0 +1,141 @@ +// +// OrderedSet.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/26/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Foundation + +// From https://github.com/apple/swift-package-manager/blob/4f69f1931b5a28bcac7a41bdb1eaddcb1223eeec/TSC/Sources/TSCBasic/OrderedSet.swift +/// An ordered set is an ordered collection of instances of `Element` in which +/// uniqueness of the objects is guaranteed. +public struct OrderedSet: Equatable, RangeReplaceableCollection { + public typealias Element = E + public typealias Index = Int + public typealias Indices = Range + + private var array: [Element] + private var set: Set + + /// Creates an empty ordered set. + public init() { + self.array = [] + self.set = Set() + } + + /// Creates an ordered set with the contents of `array`. + /// + /// If an element occurs more than once in `element`, only the first one + /// will be included. + public init(_ array: [Element]) { + self.init() + for element in array { + self.append(element) + } + } + + // MARK: Working with an ordered set + + /// The number of elements the ordered set stores. + public var count: Int { + self.array.count + } + + /// Returns `true` if the set is empty. + public var isEmpty: Bool { + self.array.isEmpty + } + + /// Returns the contents of the set as an array. + public var contents: [Element] { + self.array + } + + /// Returns `true` if the ordered set contains `member`. + public func contains(_ member: Element) -> Bool { + self.set.contains(member) + } + + /// Adds an element to the ordered set. + /// + /// If it already contains the element, then the set is unchanged. + /// + /// - returns: True if the item was inserted. + @discardableResult + public mutating func append(_ newElement: Element) -> Bool { + let inserted = self.set.insert(newElement).inserted + if inserted { + self.array.append(newElement) + } + return inserted + } + + public mutating func replaceSubrange(_ subrange: R, with newElements: C) where C: Collection, R: RangeExpression, Element == C.Element, Index == R.Bound { + self.array.replaceSubrange(subrange, with: newElements) + self.set = Set(self.array) + } + + /// Remove and return the element at the beginning of the ordered set. + public mutating func removeFirst() -> Element { + let firstElement = self.array.removeFirst() + self.set.remove(firstElement) + return firstElement + } + + /// Remove and return the element at the end of the ordered set. + public mutating func removeLast() -> Element { + let lastElement = self.array.removeLast() + self.set.remove(lastElement) + return lastElement + } + + /// Remove all elements. + public mutating func removeAll(keepingCapacity keepCapacity: Bool) { + self.array.removeAll(keepingCapacity: keepCapacity) + self.set.removeAll(keepingCapacity: keepCapacity) + } + + public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows { + try self.array.removeAll(where: shouldBeRemoved) + self.set = Set(self.array) + } + + @discardableResult + public mutating func remove(_ member: Element) -> Element? { + self.array.removeAll { $0 == member } + return self.set.remove(member) + } + + public static func == (lhs: OrderedSet, rhs: OrderedSet) -> Bool { + lhs.contents == rhs.contents + } +} + +extension OrderedSet: ExpressibleByArrayLiteral { + /// Create an instance initialized with `elements`. + /// + /// If an element occurs more than once in `element`, only the first one + /// will be included. + public init(arrayLiteral elements: Element...) { + self.init(elements) + } +} + +extension OrderedSet: RandomAccessCollection { + public var startIndex: Int { + self.contents.startIndex + } + + public var endIndex: Int { + self.contents.endIndex + } + + public subscript(index: Int) -> Element { + self.contents[index] + } +} + +extension OrderedSet: Hashable where Element: Hashable { +} diff --git a/FueledUtils/Core/TransferState.swift b/FueledUtils/Core/TransferState.swift index d1482dc9..40b4c4f3 100644 --- a/FueledUtils/Core/TransferState.swift +++ b/FueledUtils/Core/TransferState.swift @@ -7,7 +7,6 @@ // import Foundation -import ReactiveSwift /// /// Represents a transfer state, either loading or finished. @@ -49,79 +48,3 @@ public enum TransferState { } } } - -extension SignalProtocol { - /// - /// Converts a `Signal` that has a `TransferState` value into a `Signal` only returning a value when `finished()` is received. - /// - public func ignoreLoading() -> Signal - where Self.Value == TransferState - { - return self.signal.filterMap { status in - switch status { - case .loading: - return nil - case .finished(let result): - return result - } - } - } - - /// - /// Maps a `Signal` for which each value is a `TransferState` into a `TransferState` returning the mapped value. - /// - public func mapFinished(_ mapper: @escaping (Value) -> Mapped) -> Signal, Error> - where Self.Value == TransferState - { - return self.signal.map { $0.map(mapper) } - } - - /// - /// Maps a `Signal` for which each value is a `TransferState` into a `TransferState` returning a `SignalProducer` returning the mapped value. - /// - public func flatMapFinished(_ mapper: @escaping (Value) -> SignalProducer) -> Signal, Error> - where Self.Value == TransferState - { - return self.signal.flatMap(.latest) { status -> Signal, Error> in - return Signal { observer, disposable in - switch status { - case .loading(let progress): - observer.send(value: .loading(progress)) - case .finished(let result): - disposable += mapper(result) - .map { .finished($0) } - .start(observer) - } - } - } - } -} - -extension SignalProducerProtocol { - /// - /// Converts a `SignalProducer` that has a `TransferState` value into a `SignalProducer` only returning a value when `finished()` is received. - /// - public func ignoreLoading() -> SignalProducer - where Self.Value == TransferState - { - return self.producer.lift { $0.ignoreLoading() } - } - - /// - /// Maps a `SignalProducer` for which each value is a `TransferState` into a `TransferState` returning the mapped value. - /// - public func mapFinished(_ mapper: @escaping (Value) -> Mapped) -> SignalProducer, Error> - where Self.Value == TransferState - { - return self.producer.lift { $0.mapFinished(mapper) } - } - - /// - /// Maps a `SignalProducer` for which each value is a `TransferState` into a `TransferState` returning a `SignalProducer` returning the mapped value. - /// - public func flatMapFinished(_ mapper: @escaping (Value) -> SignalProducer) -> SignalProducer, Error> - where Self.Value == TransferState - { - return self.producer.lift { $0.flatMapFinished(mapper) } - } -} diff --git a/FueledUtils/Core/ActionProtocol.swift b/FueledUtils/ReactiveSwift/ActionProtocol.swift similarity index 100% rename from FueledUtils/Core/ActionProtocol.swift rename to FueledUtils/ReactiveSwift/ActionProtocol.swift diff --git a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift index 364eb0e4..7f36a343 100644 --- a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift +++ b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift @@ -30,14 +30,14 @@ public class InputCoalescingAction: ActionPro private class DisposableContainer { private let disposable: Disposable - private let count = Atomic(0) + private let count = AtomicValue(0) init(_ disposable: Disposable) { self.disposable = disposable } func add(_ lifetime: Lifetime) { - self.count.value += 1 + self.count.modify { $0 += 1 } lifetime.observeEnded { self.count.modify { $0 -= 1 diff --git a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift index 27a6f25c..e741f3bd 100644 --- a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift @@ -99,23 +99,6 @@ extension Reactive where Base: UIView { } extension Reactive where Base: UILabel { - /// - /// Update the `text` property of the label with an animation. - /// - public var animatedText: BindingTarget { - return makeBindingTarget { label, text in - label.setText(text, animated: true) - } - } - /// - /// Update the `attributedText` property of the label with an animation. - /// - public var animatedAttributedText: BindingTarget { - return makeBindingTarget { label, text in - label.setAttributedText(text, animated: true) - } - } - /// /// Update the `textAlignment` property of the label with an animation. /// diff --git a/FueledUtils/ReactiveSwift/TransferState+Reactive.swift b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift new file mode 100644 index 00000000..0b0d9b03 --- /dev/null +++ b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift @@ -0,0 +1,85 @@ +// +// LoadingStatus.swift +// AlterraCommon +// +// Created by Stéphane Copin on 10/18/17. +// Copyright © 2017 Fueled. All rights reserved. +// + +import ReactiveSwift + +extension SignalProtocol { + /// + /// Converts a `Signal` that has a `TransferState` value into a `Signal` only returning a value when `finished()` is received. + /// + public func ignoreLoading() -> Signal + where Self.Value == TransferState + { + return self.signal.filterMap { status in + switch status { + case .loading: + return nil + case .finished(let result): + return result + } + } + } + + /// + /// Maps a `Signal` for which each value is a `TransferState` into a `TransferState` returning the mapped value. + /// + public func mapFinished(_ mapper: @escaping (Value) -> Mapped) -> Signal, Error> + where Self.Value == TransferState + { + return self.signal.map { $0.map(mapper) } + } + + /// + /// Maps a `Signal` for which each value is a `TransferState` into a `TransferState` returning a `SignalProducer` returning the mapped value. + /// + public func flatMapFinished(_ mapper: @escaping (Value) -> SignalProducer) -> Signal, Error> + where Self.Value == TransferState + { + return self.signal.flatMap(.latest) { status -> Signal, Error> in + return Signal { observer, disposable in + switch status { + case .loading(let progress): + observer.send(value: .loading(progress)) + case .finished(let result): + disposable += mapper(result) + .map { .finished($0) } + .start(observer) + } + } + } + } +} + +extension SignalProducerProtocol { + /// + /// Converts a `SignalProducer` that has a `TransferState` value into a `SignalProducer` only returning a value when `finished()` is received. + /// + public func ignoreLoading() -> SignalProducer + where Self.Value == TransferState + { + return self.producer.lift { $0.ignoreLoading() } + } + + /// + /// Maps a `SignalProducer` for which each value is a `TransferState` into a `TransferState` returning the mapped value. + /// + public func mapFinished(_ mapper: @escaping (Value) -> Mapped) -> SignalProducer, Error> + where Self.Value == TransferState + { + return self.producer.lift { $0.mapFinished(mapper) } + } + + /// + /// Maps a `SignalProducer` for which each value is a `TransferState` into a `TransferState` returning a `SignalProducer` returning the mapped value. + /// + public func flatMapFinished(_ mapper: @escaping (Value) -> SignalProducer) -> SignalProducer, Error> + where Self.Value == TransferState + { + return self.producer.lift { $0.flatMapFinished(mapper) } + } +} diff --git a/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift new file mode 100644 index 00000000..bcebbbd4 --- /dev/null +++ b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift @@ -0,0 +1,38 @@ +/* +Copyright © 2019 Fueled Digital Media, LLC + +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 +import FueledUtils +import ReactiveSwift +import UIKit + +extension Reactive where Base: UILabel { + /// + /// Update the `text` property of the label with an animation. + /// + public var animatedText: BindingTarget { + return makeBindingTarget { label, text in + label.setText(text, animated: true) + } + } + /// + /// Update the `attributedText` property of the label with an animation. + /// + public var animatedAttributedText: BindingTarget { + return makeBindingTarget { label, text in + label.setAttributedText(text, animated: true) + } + } +} diff --git a/FueledUtils/SwiftUI/ForEach+IndexInfo.swift b/FueledUtils/SwiftUI/ForEach+IndexInfo.swift new file mode 100644 index 00000000..ebd20b88 --- /dev/null +++ b/FueledUtils/SwiftUI/ForEach+IndexInfo.swift @@ -0,0 +1,61 @@ +// +// ForEach+IndexInfo.swift +// RPMHelpers +// +// Created by Stéphane Copin on 2/21/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +public struct IndexInfo: Identifiable { + public let index: Index + public let id: ID + public let object: Object + + init(index: Index, object: Object, id: KeyPath) { + self.index = index + self.id = object[keyPath: id] + self.object = object + } +} + +extension IndexInfo where Object: Identifiable, ID == Object.ID { + public init(index: Index, object: Object) { + self.init( + index: index, + object: object, + id: \.self.id + ) + } +} + +extension IndexInfo where Object: Identifiable, ID == Object { + public init(index: Index, object: Object) { + self.init( + index: index, + object: object, + id: \.self + ) + } +} + +extension IndexInfo where Object: Hashable, ID == Object { + public init(index: Index, object: Object) { + self.init( + index: index, + object: object, + id: \.self + ) + } +} + +extension RandomAccessCollection { + public func withIndices(id: KeyPath) -> [IndexInfo] { + zip(self.indices, self).map { IndexInfo(index: $0, object: $1, id: id) } + } + + public func withIndices(id: KeyPath) -> [IndexInfo] { + zip(self.indices, self).map { IndexInfo(index: $0, object: $1, id: id) } + } +} diff --git a/FueledUtils/SwiftUI/FramePreferenceKey.swift b/FueledUtils/SwiftUI/FramePreferenceKey.swift new file mode 100644 index 00000000..0ee58625 --- /dev/null +++ b/FueledUtils/SwiftUI/FramePreferenceKey.swift @@ -0,0 +1,17 @@ +// +// FramePreferenceKey.swift +// RPMPlatformSupport +// +// Created by Stéphane Copin on 1/31/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +public struct FramePreferenceKey: PreferenceKey { + public static var defaultValue: CGRect = .zero + + public static func reduce(value: inout CGRect, nextValue: () -> CGRect) { + value = nextValue() + } +} diff --git a/FueledUtils/SwiftUI/View+AnyView.swift b/FueledUtils/SwiftUI/View+AnyView.swift new file mode 100644 index 00000000..406d9c38 --- /dev/null +++ b/FueledUtils/SwiftUI/View+AnyView.swift @@ -0,0 +1,15 @@ +// +// View+AnyView.swift +// RPMHelpers +// +// Created by Stéphane Copin on 1/27/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +extension View { + public func eraseToAnyView() -> AnyView { + AnyView(self) + } +} diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 21544431..502dd047 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -7,29 +7,29 @@ objects = { /* Begin PBXBuildFile section */ - C9BAC68493BD9F4548E5710C /* Pods_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */; }; + 35F8CC8FFB1D6B2E7C4D70DE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; + 54D079E22C0F14675247FE66 /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; + F463C74724183914000A0B29 /* EmptySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C74624183914000A0B29 /* EmptySpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; - 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.release.xcconfig"; sourceTree = ""; }; - 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.release.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.release.xcconfig"; sourceTree = ""; }; - 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests/Pods-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; - 30FF6F506B3687CD71C7CA63 /* Pods-FueledUtils_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Tests/Pods-FueledUtils_Tests.debug.xcconfig"; sourceTree = ""; }; - 50C905A2E5F29D21C72DDE72 /* Pods-FueledUtils_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.release.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.release.xcconfig"; sourceTree = ""; }; + 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; + 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; + 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-ReactiveSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 68EB228E5A42939F3F5A53FA /* Pods-FueledUtils_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtils_Example.debug.xcconfig"; path = "Target Support Files/Pods-FueledUtils_Example/Pods-FueledUtils_Example.debug.xcconfig"; sourceTree = ""; }; - 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtils_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; + 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-SwiftUI.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + F463C74624183914000A0B29 /* EmptySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySpec.swift; sourceTree = ""; }; F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -37,7 +37,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C9BAC68493BD9F4548E5710C /* Pods_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, + 35F8CC8FFB1D6B2E7C4D70DE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F463C74124183913000A0B29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 54D079E22C0F14675247FE66 /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -47,36 +55,24 @@ 29F7E676D0DA9F23C4893ECA /* Pods */ = { isa = PBXGroup; children = ( - 68EB228E5A42939F3F5A53FA /* Pods-FueledUtils_Example.debug.xcconfig */, - 50C905A2E5F29D21C72DDE72 /* Pods-FueledUtils_Example.release.xcconfig */, - 30FF6F506B3687CD71C7CA63 /* Pods-FueledUtils_Tests.debug.xcconfig */, - 2341FB1DCB88A2B8EAA3623E /* Pods-FueledUtils_Tests.release.xcconfig */, - 2CBF3CD03C9A80EA908B4045 /* Pods-FueledUtilsTests.debug.xcconfig */, - 2C68A50B9543E04CC7D6CC8C /* Pods-FueledUtilsTests.release.xcconfig */, - 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, - 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */, + 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, + 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, + EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, + 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, ); path = Pods; sourceTree = ""; }; - 3F2C90138F935B412FFC6818 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 8719A42FB9602997CB2CB8D8 /* Pods_FueledUtils_Example.framework */, - FD0E4C106518D26D883AD1E2 /* Pods_FueledUtilsTests_ReactiveSwift.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( 607FACF51AFB993E008FA782 /* Podspec Metadata */, F463C739241835DD000A0B29 /* ReactiveSwift */, + F463C74524183914000A0B29 /* SwiftUI */, F463C73E241835EA000A0B29 /* Supporting Files */, 607FACD11AFB9204008FA782 /* Products */, 29F7E676D0DA9F23C4893ECA /* Pods */, - 3F2C90138F935B412FFC6818 /* Frameworks */, + 8CC62AE44D59DE05C174CDDF /* Frameworks */, ); sourceTree = ""; }; @@ -84,6 +80,7 @@ isa = PBXGroup; children = ( 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */, + F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */, ); name = Products; sourceTree = ""; @@ -98,6 +95,15 @@ name = "Podspec Metadata"; sourceTree = ""; }; + 8CC62AE44D59DE05C174CDDF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, + 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; F463C739241835DD000A0B29 /* ReactiveSwift */ = { isa = PBXGroup; children = ( @@ -115,6 +121,14 @@ path = "Supporting Files"; sourceTree = ""; }; + F463C74524183914000A0B29 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + F463C74624183914000A0B29 /* EmptySpec.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -122,11 +136,11 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */; buildPhases = ( - 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */, + C320683D0E6DE2C24E3CF01D /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, 607FACE21AFB9204008FA782 /* Frameworks */, 607FACE31AFB9204008FA782 /* Resources */, - 39A7A643C6627B4368EAE656 /* [CP] Embed Pods Frameworks */, + AD299C2F8AF720F581AA8A6C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -137,13 +151,32 @@ productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + F463C74324183913000A0B29 /* FueledUtilsTests-SwiftUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */; + buildPhases = ( + 547374A9AA641604D86F01D7 /* [CP] Check Pods Manifest.lock */, + F463C74024183913000A0B29 /* Sources */, + F463C74124183913000A0B29 /* Frameworks */, + F463C74224183913000A0B29 /* Resources */, + C5C0B0702E7860682A563F05 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "FueledUtilsTests-SwiftUI"; + productName = "FueledUtilsTests-Combine"; + productReference = F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 607FACC81AFB9204008FA782 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0830; + LastSwiftUpdateCheck = 1140; LastUpgradeCheck = 1140; ORGANIZATIONNAME = CocoaPods; TargetAttributes = { @@ -151,6 +184,11 @@ CreatedOnToolsVersion = 6.3.1; LastSwiftMigration = 0900; }; + F463C74324183913000A0B29 = { + CreatedOnToolsVersion = 11.4; + DevelopmentTeam = 2GVJ6JS822; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FueledUtils" */; @@ -167,6 +205,7 @@ projectRoot = ""; targets = ( 607FACE41AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift */, + F463C74324183913000A0B29 /* FueledUtilsTests-SwiftUI */, ); }; /* End PBXProject section */ @@ -179,19 +218,48 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F463C74224183913000A0B29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 39A7A643C6627B4368EAE656 /* [CP] Embed Pods Frameworks */ = { + 547374A9AA641604D86F01D7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils/FueledUtils.framework", - "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + AD299C2F8AF720F581AA8A6C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FueledUtils-Core-ReactiveSwift-ReactiveSwiftUIKit-UIKit-iOS8/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/Nimble-iOS8.0/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick-iOS8.0/Quick.framework", "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", "${BUILT_PRODUCTS_DIR}/ReactiveSwift/ReactiveSwift.framework", ); @@ -205,10 +273,10 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FueledUtilsTests-ReactiveSwift/Pods-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 57455DDAAC86F015B5F34C12 /* [CP] Check Pods Manifest.lock */ = { + C320683D0E6DE2C24E3CF01D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -223,13 +291,35 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + C5C0B0702E7860682A563F05 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FueledUtils-Combine-Core-SwiftUI-UIKit-iOS13-iOS8/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -242,6 +332,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F463C74024183913000A0B29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F463C74724183914000A0B29 /* EmptySpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ @@ -352,7 +450,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1225DAC720CDF115D2FB98EA /* Pods-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; + baseConfigurationReference = 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -367,13 +465,13 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 95EFAC7ABA3D5AB2C3F75825 /* Pods-FueledUtilsTests-ReactiveSwift.release.xcconfig */; + baseConfigurationReference = 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -384,7 +482,64 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + F463C74A24183914000A0B29 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 2GVJ6JS822; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtilsTests-Combine"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; + }; + name = Debug; + }; + F463C74B24183914000A0B29 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2GVJ6JS822; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtilsTests-Combine"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; @@ -409,6 +564,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F463C74A24183914000A0B29 /* Debug */, + F463C74B24183914000A0B29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 607FACC81AFB9204008FA782 /* Project object */; diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme new file mode 100644 index 00000000..4b05ee9d --- /dev/null +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme new file mode 100644 index 00000000..0ce61329 --- /dev/null +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Podfile b/Tests/Podfile index ebfb8d57..70245ba4 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -1,10 +1,20 @@ inhibit_all_warnings! use_frameworks! -target 'FueledUtilsTests-ReactiveSwift' do - platform :ios, '8.0' +abstract_target 'Common' do + pod 'Quick', '~> 2.0' + pod 'Nimble', '~> 8.0' - pod 'FueledUtils/ReactiveSwift', path: '../' - pod 'Quick', '~> 1.2.0' - pod 'Nimble', '~> 7.0' + target 'FueledUtilsTests-ReactiveSwift' do + platform :ios, '8.0' + + pod 'FueledUtils/ReactiveSwiftUIKit', path: '../' + end + + target 'FueledUtilsTests-SwiftUI' do + platform :ios, '13.0' + + pod 'FueledUtils/SwiftUI', path: '../' + pod 'FueledUtils/UIKit', path: '../' + end end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 1ba57f0a..f23ae737 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,19 +1,35 @@ PODS: + - FueledUtils/Combine (2.0.3): + - FueledUtils/iOS13 - FueledUtils/Core (2.0.3) - - FueledUtils/ReactiveSwift (2.0.3): + - FueledUtils/iOS13 (2.0.3): + - FueledUtils/Core + - FueledUtils/iOS8 (2.0.3): - FueledUtils/Core + - FueledUtils/ReactiveSwift (2.0.3): + - FueledUtils/iOS8 - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) - - Nimble (7.3.4) - - Quick (1.2.0) + - FueledUtils/ReactiveSwiftUIKit (2.0.3): + - FueledUtils/ReactiveSwift + - FueledUtils/UIKit + - FueledUtils/SwiftUI (2.0.3): + - FueledUtils/Combine + - FueledUtils/Core + - FueledUtils/UIKit (2.0.3): + - FueledUtils/iOS8 + - Nimble (8.0.5) + - Quick (2.2.0) - ReactiveCocoa (10.2.0): - ReactiveSwift (~> 6.2) - - ReactiveSwift (6.2.0) + - ReactiveSwift (6.2.1) DEPENDENCIES: - - FueledUtils/ReactiveSwift (from `../`) - - Nimble (~> 7.0) - - Quick (~> 1.2.0) + - FueledUtils/ReactiveSwiftUIKit (from `../`) + - FueledUtils/SwiftUI (from `../`) + - FueledUtils/UIKit (from `../`) + - Nimble (~> 8.0) + - Quick (~> 2.0) SPEC REPOS: trunk: @@ -27,12 +43,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: d78f55645ec7f48564411496d6ce393e222e5ec3 - Nimble: 051e3d8912d40138fa5591c78594f95fb172af37 - Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 + FueledUtils: d1c92d23ffadddab6302ff5cfe55b8331aaf7b9c + Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 + Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 - ReactiveSwift: 3dc8800378110b13d40b46ab362afa9f6bcffc6c + ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 -PODFILE CHECKSUM: d59d41b7968395125eb11a800df5f1e1d9affdfc +PODFILE CHECKSUM: abdfd3aaf0821d915b1d8ec1e01f20583cd11b0f -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1 diff --git a/Tests/SwiftUI/EmptySpec.swift b/Tests/SwiftUI/EmptySpec.swift new file mode 100644 index 00000000..db861734 --- /dev/null +++ b/Tests/SwiftUI/EmptySpec.swift @@ -0,0 +1,20 @@ +// +// FueledUtilsTests_Combine.swift +// FueledUtilsTests-Combine +// +// Created by Stéphane Copin on 3/10/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Quick +import Nimble + +class CoalescingActionSpec: QuickSpec { + override func spec() { + describe("Nothing") { + it("should pass") { + expect(true) == true + } + } + } +} From 8596f34cacb1651766e0ee18b9bc41d9c7674cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 17:40:02 -0400 Subject: [PATCH 04/89] chore(deprecation): remove deprecated functions --- FueledUtils/Core/FoundationExtensions.swift | 28 ----------- FueledUtils/Core/SequenceExtensions.swift | 5 -- FueledUtils/Core/StringExtensions.swift | 10 ---- FueledUtils/ReactiveSwift/LoadingState.swift | 22 --------- .../ReactiveCocoaExtensions.swift | 12 ----- .../ReactiveLifetimeProvider.swift | 48 ------------------- 6 files changed, 125 deletions(-) delete mode 100644 FueledUtils/Core/FoundationExtensions.swift delete mode 100644 FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift diff --git a/FueledUtils/Core/FoundationExtensions.swift b/FueledUtils/Core/FoundationExtensions.swift deleted file mode 100644 index efa8db91..00000000 --- a/FueledUtils/Core/FoundationExtensions.swift +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC - -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 - -extension Bool { - /// - /// **Unavailable**: Please use `toggle()` instead. - /// - /// Refer to the documentation for `toggle()` for more info. - /// - @available(*, unavailable, renamed: "toggle()") - public mutating func flip() { - fatalError() - } -} diff --git a/FueledUtils/Core/SequenceExtensions.swift b/FueledUtils/Core/SequenceExtensions.swift index 2bcb9145..63a3a633 100644 --- a/FueledUtils/Core/SequenceExtensions.swift +++ b/FueledUtils/Core/SequenceExtensions.swift @@ -91,9 +91,4 @@ extension Sequence { } return result } - - @available(*, unavailable, renamed: "first(where:)") - func findFirst(_ predicate: (Iterator.Element) -> Bool) -> Iterator.Element? { - return self.first(where: predicate) - } } diff --git a/FueledUtils/Core/StringExtensions.swift b/FueledUtils/Core/StringExtensions.swift index 9b1572b0..db7dc5f6 100644 --- a/FueledUtils/Core/StringExtensions.swift +++ b/FueledUtils/Core/StringExtensions.swift @@ -25,16 +25,6 @@ extension StringProtocol { return (String(self) as NSString).length } - /// - /// **Unavailable**: Please use `nsRange` instead. - /// - /// Refer to the documentation for `nsRange` for more info. - /// - @available(*, unavailable, renamed: "nsRange") - public var fullRange: NSRange { - return NSRange(location: 0, length: nsLength) - } - /// /// Returns `NSRange(location: 0, length: nsLength)` for usage with Objective-C APIs. /// diff --git a/FueledUtils/ReactiveSwift/LoadingState.swift b/FueledUtils/ReactiveSwift/LoadingState.swift index 7f3793c8..541ec0e6 100644 --- a/FueledUtils/ReactiveSwift/LoadingState.swift +++ b/FueledUtils/ReactiveSwift/LoadingState.swift @@ -44,16 +44,6 @@ public enum LoadingState { } } - /// - /// **Unavailable**: Please use `isLoading` instead. - /// - /// Refer to the documentation for `isLoading` for more info. - /// - @available(*, unavailable, renamed: "isLoading") - public var loading: Bool { - return self.isLoading - } - /// /// If the current state is `.loading`, returns `true`. If not, returns `false` /// @@ -67,18 +57,6 @@ public enum LoadingState { } extension ActionProtocol { - /// - /// **Unavailable**: Please use `getSafely(at:)` instead. - /// - /// Refer to the documentation for `getSafely(at:)` for more info. - /// - @available(*, unavailable, renamed: "loadingState") - // The unused parameter allows to bypass the compiler error "Invalid redeclaration of 'loadingState'", - // while retaining backward compatibility - public func loadingState(_ unused: Void = ()) -> SignalProducer, Never> { - fatalError() - } - /// /// Returns a `SignalProducer` whose events corresponds to the current loading state of the action. /// Please refer to `LoadingState` for more info. diff --git a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift index e741f3bd..410d4dec 100644 --- a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift @@ -122,18 +122,6 @@ extension Reactive where Base: UIViewController { } } -@available(iOS 9.0, *) -extension Reactive where Base: UIStackView { - /// - /// **Unavailable**: Use `subview.reactive.isHidden <~ ` instead. - /// Add/remove/modify the order of the arranged subviews by specified the subview. - /// - @available(*, unavailable, message: "Use `subview.reactive.isHidden <~ ` instead") - public func isArranged(_ subview: UIView, at index: Int) -> BindingTarget { - fatalError() - } -} - #if os(iOS) extension Reactive where Base: UINavigationItem { /// diff --git a/FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift b/FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift deleted file mode 100644 index 5fa490d3..00000000 --- a/FueledUtils/ReactiveSwift/ReactiveLifetimeProvider.swift +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC - -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 ReactiveCocoa -import ReactiveSwift - -/// -/// **Unavailable**: Please just make your type conform to `ReactiveExtensionsProvider`. -/// The goal of this protocol is to add RAC-style `reactive` proxy to Swift objects. -/// -@available(*, unavailable, renamed: "ReactiveExtensionsProvider") -public protocol ReactiveLifetimeProvider: ReactiveExtensionsProvider { - /// - /// The lifetime token associated with the instance. - /// - var lifetimeToken: Lifetime.Token { get } -} - -/// -/// **Unavailable**: Please just make your type a `class` that conform to `ReactiveExtensionsProvider` instead; there is no need to inherit from this anymore. -/// This base class adds RAC-style `reactive` proxy to Swift objects. -/// -@available(*, unavailable, message: "Make your type a class that conforms to ReactiveLifetimeProvider instead") -open class Lifetimed { -} - -extension Reactive where Base: AnyObject { - /// - /// **Unavailable**: Use `Lifetime.of()` instead - /// Get the lifetime associated with the instance. - /// - @available(*, unavailable, renamed: "Lifetime.of()") - public var lifetime: Lifetime { - return Lifetime.of(self.base) - } -} From 68f886102b782b687e66c9236103e668ff08d548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 17:44:06 -0400 Subject: [PATCH 05/89] feat(combine/operators): move operators to its own subspec --- FueledUtils.podspec | 8 +- FueledUtils/Combine/Action.swift | 12 +- .../Combine+Operators.swift | 0 FueledUtils/Core/Lock.swift | 10 +- Tests/FueledUtils.xcodeproj/project.pbxproj | 116 +++++++++--------- Tests/Podfile | 1 + Tests/Podfile.lock | 7 +- 7 files changed, 82 insertions(+), 72 deletions(-) rename FueledUtils/{Combine => CombineOperators}/Combine+Operators.swift (100%) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 030ef404..37adcb58 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -57,10 +57,16 @@ Pod::Spec.new do |s| s.subspec 'Combine' do |s| s.dependency 'FueledUtils/iOS13' - + s.source_files = 'FueledUtils/Combine/**/*.swift' end + s.subspec 'CombineOperators' do |s| + s.dependency 'FueledUtils/Combine' + + s.source_files = 'FueledUtils/CombineOperators/**/*.swift' + end + s.subspec 'SwiftUI' do |s| s.dependency 'FueledUtils/Core' s.dependency 'FueledUtils/Combine' diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 66c8a5a9..923fcfac 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -59,18 +59,18 @@ public final class Action { self.values = values.eraseToAnyPublisher() self.errors = errors.eraseToAnyPublisher() - let isExecutingLock = DispatchSemaphore(value: 1) + let isExecutingLock = Lock() self.execute = { action, input -> AnyPublisher> in - isExecutingLock.wait() + isExecutingLock.lock() if !action.isEnabled || action.isExecuting { - isExecutingLock.signal() + isExecutingLock.unlock() return Fail(error: .disabled) .eraseToAnyPublisher() } action.isExecuting = true - isExecutingLock.signal() + isExecutingLock.unlock() return execute(input) .handleEvents( receiveOutput: { value in @@ -79,9 +79,9 @@ public final class Action { receiveCompletion: { [weak action] completion in switch completion { case .finished: - isExecutingLock.wait() + isExecutingLock.lock() action?.isExecuting = false - isExecutingLock.signal() + isExecutingLock.unlock() case .failure(let error): errors.send(error) } diff --git a/FueledUtils/Combine/Combine+Operators.swift b/FueledUtils/CombineOperators/Combine+Operators.swift similarity index 100% rename from FueledUtils/Combine/Combine+Operators.swift rename to FueledUtils/CombineOperators/Combine+Operators.swift diff --git a/FueledUtils/Core/Lock.swift b/FueledUtils/Core/Lock.swift index 1dfbfdef..cfbdf91f 100644 --- a/FueledUtils/Core/Lock.swift +++ b/FueledUtils/Core/Lock.swift @@ -69,10 +69,10 @@ private struct CocoaLock: LockImplementation { } } -final class Lock { +public final class Lock { private var lockImplementation: LockImplementation - init() { + public init() { if #available(iOS 10.0, *) { self.lockImplementation = UnfairLock() } else { @@ -80,15 +80,15 @@ final class Lock { } } - func lock() { + public func lock() { self.lockImplementation.lock() } - func `try`() -> Bool { + public func `try`() -> Bool { self.lockImplementation.try() } - func unlock() { + public func unlock() { self.lockImplementation.unlock() } } diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 502dd047..c20c30c2 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -7,23 +7,23 @@ objects = { /* Begin PBXBuildFile section */ - 35F8CC8FFB1D6B2E7C4D70DE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; - 54D079E22C0F14675247FE66 /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; + 2226E30A096757B47E86AB53 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; + C2A10C8364626483B976411E /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; F463C74724183914000A0B29 /* EmptySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C74624183914000A0B29 /* EmptySpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; - 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; - 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; + 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; + 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-ReactiveSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; + BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; + D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -37,7 +37,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 35F8CC8FFB1D6B2E7C4D70DE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, + 2226E30A096757B47E86AB53 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -45,20 +45,29 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 54D079E22C0F14675247FE66 /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, + C2A10C8364626483B976411E /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 02A127E0260A9C408A650AFD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, + D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 29F7E676D0DA9F23C4893ECA /* Pods */ = { isa = PBXGroup; children = ( - 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, - 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, - EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, - 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, + 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, + BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, + D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, + 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -72,7 +81,7 @@ F463C73E241835EA000A0B29 /* Supporting Files */, 607FACD11AFB9204008FA782 /* Products */, 29F7E676D0DA9F23C4893ECA /* Pods */, - 8CC62AE44D59DE05C174CDDF /* Frameworks */, + 02A127E0260A9C408A650AFD /* Frameworks */, ); sourceTree = ""; }; @@ -95,15 +104,6 @@ name = "Podspec Metadata"; sourceTree = ""; }; - 8CC62AE44D59DE05C174CDDF /* Frameworks */ = { - isa = PBXGroup; - children = ( - 319DFB4D8863AB42E6999A5C /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, - 77233DE5B5C04AC4965CE162 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; F463C739241835DD000A0B29 /* ReactiveSwift */ = { isa = PBXGroup; children = ( @@ -136,11 +136,11 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */; buildPhases = ( - C320683D0E6DE2C24E3CF01D /* [CP] Check Pods Manifest.lock */, + B5C5ECD5F611B11872D34C4A /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, 607FACE21AFB9204008FA782 /* Frameworks */, 607FACE31AFB9204008FA782 /* Resources */, - AD299C2F8AF720F581AA8A6C /* [CP] Embed Pods Frameworks */, + 8C7652B0CEBBDD052B99C924 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -155,11 +155,11 @@ isa = PBXNativeTarget; buildConfigurationList = F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */; buildPhases = ( - 547374A9AA641604D86F01D7 /* [CP] Check Pods Manifest.lock */, + AC6F6AE7495CB52DF3C2EB32 /* [CP] Check Pods Manifest.lock */, F463C74024183913000A0B29 /* Sources */, F463C74124183913000A0B29 /* Frameworks */, F463C74224183913000A0B29 /* Resources */, - C5C0B0702E7860682A563F05 /* [CP] Embed Pods Frameworks */, + E807AE5D00D79221217A1841 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -228,29 +228,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 547374A9AA641604D86F01D7 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - AD299C2F8AF720F581AA8A6C /* [CP] Embed Pods Frameworks */ = { + 8C7652B0CEBBDD052B99C924 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -276,7 +254,29 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - C320683D0E6DE2C24E3CF01D /* [CP] Check Pods Manifest.lock */ = { + AC6F6AE7495CB52DF3C2EB32 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B5C5ECD5F611B11872D34C4A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -298,14 +298,14 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - C5C0B0702E7860682A563F05 /* [CP] Embed Pods Frameworks */ = { + E807AE5D00D79221217A1841 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-Combine-Core-SwiftUI-UIKit-iOS13-iOS8/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/FueledUtils-2d777442/FueledUtils.framework", "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", ); @@ -450,7 +450,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0FF281871C55963AB59A9F46 /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; + baseConfigurationReference = 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -471,7 +471,7 @@ }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 43ADD2D6673C0C08F94766B4 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; + baseConfigurationReference = BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -488,7 +488,7 @@ }; F463C74A24183914000A0B29 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EDAB8F154FE09F6F6E21C837 /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; + baseConfigurationReference = D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -518,7 +518,7 @@ }; F463C74B24183914000A0B29 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4D7F0D31E66814938C855124 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; + baseConfigurationReference = 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/Tests/Podfile b/Tests/Podfile index 70245ba4..5efeb07d 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -14,6 +14,7 @@ abstract_target 'Common' do target 'FueledUtilsTests-SwiftUI' do platform :ios, '13.0' + pod 'FueledUtils/CombineOperators', path: '../' pod 'FueledUtils/SwiftUI', path: '../' pod 'FueledUtils/UIKit', path: '../' end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index f23ae737..d5bcf641 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,6 +1,8 @@ PODS: - FueledUtils/Combine (2.0.3): - FueledUtils/iOS13 + - FueledUtils/CombineOperators (2.0.3): + - FueledUtils/Combine - FueledUtils/Core (2.0.3) - FueledUtils/iOS13 (2.0.3): - FueledUtils/Core @@ -25,6 +27,7 @@ PODS: - ReactiveSwift (6.2.1) DEPENDENCIES: + - FueledUtils/CombineOperators (from `../`) - FueledUtils/ReactiveSwiftUIKit (from `../`) - FueledUtils/SwiftUI (from `../`) - FueledUtils/UIKit (from `../`) @@ -43,12 +46,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: d1c92d23ffadddab6302ff5cfe55b8331aaf7b9c + FueledUtils: 481701ad929bc05aebf62c0d8eac3c5aeaeac963 Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 -PODFILE CHECKSUM: abdfd3aaf0821d915b1d8ec1e01f20583cd11b0f +PODFILE CHECKSUM: 8c906577889305cc0ee68800bda92e89168239fc COCOAPODS: 1.9.1 From 554cb77ceb1b8a94375ebd7c9e3dfb80e57c04f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 17:58:59 -0400 Subject: [PATCH 06/89] chore(code): update license to make it easier to read, code to add Apache Header and update PodSpec to match v3.0 --- FueledUtils.podspec | 2 +- FueledUtils/Combine/Action.swift | 14 +- FueledUtils/Combine/Binding+KeyPath.swift | 14 +- .../Combine/ObservableObjectExtensions.swift | 14 +- .../Combine/Published+PublisherInit.swift | 14 +- .../Combine/Publisher+CombinePrevious.swift | 14 +- FueledUtils/Combine/PublisherExtensions.swift | 14 +- .../Combine/PublishersExtensions.swift | 14 +- .../CombineOperators/Combine+Operators.swift | 14 +- FueledUtils/Core/Atomic.swift | 14 +- FueledUtils/Core/Lock.swift | 14 +- .../Core/NSDecimalNumberOperators.swift | 27 +- FueledUtils/Core/OptionalProtocol.swift | 14 +- FueledUtils/Core/OrderedSet.swift | 14 +- FueledUtils/Core/Regex.swift | 27 +- FueledUtils/Core/SequenceExtensions.swift | 27 +- FueledUtils/Core/StringExtensions.swift | 27 +- FueledUtils/Core/TransferState.swift | 14 +- .../ReactiveSwift/ActionProtocol.swift | 14 +- .../ReactiveSwift/InputCoalescingAction.swift | 14 +- FueledUtils/ReactiveSwift/LoadingState.swift | 27 +- .../ReactiveCocoaExtensions.swift | 27 +- .../ReactiveSwiftExtensions.swift | 27 +- .../TransferState+Reactive.swift | 14 +- .../ReactiveSwift/TypedSerialDisposable.swift | 14 +- .../ReactiveSwiftUIKit/SignalingAlert.swift | 27 +- .../UIReactiveExtensions.swift | 27 +- FueledUtils/SwiftUI/ForEach+IndexInfo.swift | 14 +- FueledUtils/SwiftUI/FramePreferenceKey.swift | 14 +- FueledUtils/SwiftUI/View+AnyView.swift | 14 +- .../UIKit/ButtonWithTitleAdjustment.swift | 27 +- FueledUtils/UIKit/CollectionExtensions.swift | 27 +- .../UIKit/DecoratingTextFieldDelegate.swift | 27 +- FueledUtils/UIKit/DimmingButton.swift | 27 +- FueledUtils/UIKit/GradientView.swift | 14 + FueledUtils/UIKit/HairlineView.swift | 27 +- FueledUtils/UIKit/KeyboardInsetHelper.swift | 27 +- .../UIKit/LabelWithTitleAdjustment.swift | 27 +- FueledUtils/UIKit/ScrollViewPage.swift | 27 +- FueledUtils/UIKit/SetRootViewController.swift | 27 +- FueledUtils/UIKit/UIExtensions.swift | 27 +- LICENSE | 254 ++++++++++++++---- .../ReactiveSwift/CoalescingActionSpec.swift | 14 +- .../ReactiveSwiftExtensionsSpec.swift | 14 +- Tests/SwiftUI/EmptySpec.swift | 14 +- 45 files changed, 693 insertions(+), 412 deletions(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 37adcb58..d565a266 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'FueledUtils' - s.version = '2.0.3' + s.version = '3.0' s.summary = 'A collection of utilities used at Fueled' s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' s.swift_version = '5' diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 923fcfac..561bb7b7 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Action.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/16/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine import SwiftUI diff --git a/FueledUtils/Combine/Binding+KeyPath.swift b/FueledUtils/Combine/Binding+KeyPath.swift index 87cf1305..8669b5f3 100644 --- a/FueledUtils/Combine/Binding+KeyPath.swift +++ b/FueledUtils/Combine/Binding+KeyPath.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Binding+KeyPath.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/23/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/Combine/ObservableObjectExtensions.swift b/FueledUtils/Combine/ObservableObjectExtensions.swift index 585fa40f..39ec2022 100644 --- a/FueledUtils/Combine/ObservableObjectExtensions.swift +++ b/FueledUtils/Combine/ObservableObjectExtensions.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// ObservableObject+Link.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/11/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/Published+PublisherInit.swift b/FueledUtils/Combine/Published+PublisherInit.swift index 52f7e29e..86f029ff 100644 --- a/FueledUtils/Combine/Published+PublisherInit.swift +++ b/FueledUtils/Combine/Published+PublisherInit.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Published+PublisherInit.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/23/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/Publisher+CombinePrevious.swift b/FueledUtils/Combine/Publisher+CombinePrevious.swift index 9771ad8d..7c79daf7 100644 --- a/FueledUtils/Combine/Publisher+CombinePrevious.swift +++ b/FueledUtils/Combine/Publisher+CombinePrevious.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Combine+CombinePrevious.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/18/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/PublisherExtensions.swift b/FueledUtils/Combine/PublisherExtensions.swift index ad2b85df..cba448f9 100644 --- a/FueledUtils/Combine/PublisherExtensions.swift +++ b/FueledUtils/Combine/PublisherExtensions.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Publisher+sink.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/23/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index 6a3b1b24..e16a22da 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// PublishersExtensions.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/11/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/CombineOperators/Combine+Operators.swift b/FueledUtils/CombineOperators/Combine+Operators.swift index 2600c9fd..09e7b561 100644 --- a/FueledUtils/CombineOperators/Combine+Operators.swift +++ b/FueledUtils/CombineOperators/Combine+Operators.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Combine+Operators.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/11/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift index de078271..3d11e256 100644 --- a/FueledUtils/Core/Atomic.swift +++ b/FueledUtils/Core/Atomic.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Atomic.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/16/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 diff --git a/FueledUtils/Core/Lock.swift b/FueledUtils/Core/Lock.swift index cfbdf91f..1437daab 100644 --- a/FueledUtils/Core/Lock.swift +++ b/FueledUtils/Core/Lock.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Lock.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/23/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 diff --git a/FueledUtils/Core/NSDecimalNumberOperators.swift b/FueledUtils/Core/NSDecimalNumberOperators.swift index db975ed6..5c892b9d 100644 --- a/FueledUtils/Core/NSDecimalNumberOperators.swift +++ b/FueledUtils/Core/NSDecimalNumberOperators.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 extension NSDecimalNumber: Comparable { diff --git a/FueledUtils/Core/OptionalProtocol.swift b/FueledUtils/Core/OptionalProtocol.swift index d6382a09..3341073f 100644 --- a/FueledUtils/Core/OptionalProtocol.swift +++ b/FueledUtils/Core/OptionalProtocol.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// OptionalProtocol.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/9/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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. public protocol OptionalProtocol { associatedtype Wrapped diff --git a/FueledUtils/Core/OrderedSet.swift b/FueledUtils/Core/OrderedSet.swift index dcd4d920..ddc12f49 100644 --- a/FueledUtils/Core/OrderedSet.swift +++ b/FueledUtils/Core/OrderedSet.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// OrderedSet.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/26/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 diff --git a/FueledUtils/Core/Regex.swift b/FueledUtils/Core/Regex.swift index fe5528ff..ec8c919c 100644 --- a/FueledUtils/Core/Regex.swift +++ b/FueledUtils/Core/Regex.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 /// diff --git a/FueledUtils/Core/SequenceExtensions.swift b/FueledUtils/Core/SequenceExtensions.swift index 63a3a633..3cd29bbe 100644 --- a/FueledUtils/Core/SequenceExtensions.swift +++ b/FueledUtils/Core/SequenceExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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. -*/ extension Sequence { /// /// Transforms the sequence into a dictionary grouped by the specified Key type. diff --git a/FueledUtils/Core/StringExtensions.swift b/FueledUtils/Core/StringExtensions.swift index db7dc5f6..46447fdd 100644 --- a/FueledUtils/Core/StringExtensions.swift +++ b/FueledUtils/Core/StringExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 extension StringProtocol { diff --git a/FueledUtils/Core/TransferState.swift b/FueledUtils/Core/TransferState.swift index 40b4c4f3..4dbb89d0 100644 --- a/FueledUtils/Core/TransferState.swift +++ b/FueledUtils/Core/TransferState.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// LoadingStatus.swift -// AlterraCommon +// 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 // -// Created by Stéphane Copin on 10/18/17. -// Copyright © 2017 Fueled. All rights reserved. +// 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 diff --git a/FueledUtils/ReactiveSwift/ActionProtocol.swift b/FueledUtils/ReactiveSwift/ActionProtocol.swift index e32eb7fa..aa67d279 100644 --- a/FueledUtils/ReactiveSwift/ActionProtocol.swift +++ b/FueledUtils/ReactiveSwift/ActionProtocol.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// ActionProtocol.swift -// FueledUtils +// 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 // -// Created by Stéphane Copin on 1/23/19. -// Copyright © 2019 Fueled. All rights reserved. +// 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 import ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift index 7f36a343..254f15a9 100644 --- a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift +++ b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// InputCoalescingAction.swift -// FueledUtils +// 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 // -// Created by Stéphane Copin on 4/22/16. -// Copyright © 2016 Fueled. All rights reserved. +// 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 ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/LoadingState.swift b/FueledUtils/ReactiveSwift/LoadingState.swift index 541ec0e6..67ee5c3e 100644 --- a/FueledUtils/ReactiveSwift/LoadingState.swift +++ b/FueledUtils/ReactiveSwift/LoadingState.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift index 410d4dec..a7de6a92 100644 --- a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import ReactiveCocoa import ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift index a5aea499..9ae2a190 100644 --- a/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/TransferState+Reactive.swift b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift index 0b0d9b03..3f1f9be4 100644 --- a/FueledUtils/ReactiveSwift/TransferState+Reactive.swift +++ b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// LoadingStatus.swift -// AlterraCommon +// 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 // -// Created by Stéphane Copin on 10/18/17. -// Copyright © 2017 Fueled. All rights reserved. +// 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 ReactiveSwift diff --git a/FueledUtils/ReactiveSwift/TypedSerialDisposable.swift b/FueledUtils/ReactiveSwift/TypedSerialDisposable.swift index cc9ab83a..57282be4 100644 --- a/FueledUtils/ReactiveSwift/TypedSerialDisposable.swift +++ b/FueledUtils/ReactiveSwift/TypedSerialDisposable.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// TypedSerialDisposable.swift -// FueledUtils +// 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 // -// Created by Stéphane Copin on 11/7/18. -// Copyright © 2018 Fueled. All rights reserved. +// 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 ReactiveSwift diff --git a/FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift b/FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift index 404d3e4b..f15cb756 100644 --- a/FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift +++ b/FueledUtils/ReactiveSwiftUIKit/SignalingAlert.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import ReactiveCocoa import ReactiveSwift diff --git a/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift index bcebbbd4..86f6ebf5 100644 --- a/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift +++ b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import FueledUtils import ReactiveSwift diff --git a/FueledUtils/SwiftUI/ForEach+IndexInfo.swift b/FueledUtils/SwiftUI/ForEach+IndexInfo.swift index ebd20b88..ec7f8795 100644 --- a/FueledUtils/SwiftUI/ForEach+IndexInfo.swift +++ b/FueledUtils/SwiftUI/ForEach+IndexInfo.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// ForEach+IndexInfo.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 2/21/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/SwiftUI/FramePreferenceKey.swift b/FueledUtils/SwiftUI/FramePreferenceKey.swift index 0ee58625..b50c8a73 100644 --- a/FueledUtils/SwiftUI/FramePreferenceKey.swift +++ b/FueledUtils/SwiftUI/FramePreferenceKey.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// FramePreferenceKey.swift -// RPMPlatformSupport +// 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 // -// Created by Stéphane Copin on 1/31/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/SwiftUI/View+AnyView.swift b/FueledUtils/SwiftUI/View+AnyView.swift index 406d9c38..fffcfeb1 100644 --- a/FueledUtils/SwiftUI/View+AnyView.swift +++ b/FueledUtils/SwiftUI/View+AnyView.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// View+AnyView.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 1/27/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/UIKit/ButtonWithTitleAdjustment.swift b/FueledUtils/UIKit/ButtonWithTitleAdjustment.swift index 9453c9ec..4bd7fc97 100644 --- a/FueledUtils/UIKit/ButtonWithTitleAdjustment.swift +++ b/FueledUtils/UIKit/ButtonWithTitleAdjustment.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 UIKit /// diff --git a/FueledUtils/UIKit/CollectionExtensions.swift b/FueledUtils/UIKit/CollectionExtensions.swift index d38f4148..cc1018de 100644 --- a/FueledUtils/UIKit/CollectionExtensions.swift +++ b/FueledUtils/UIKit/CollectionExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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. -*/ extension Collection { /// /// **Unavailable**: Please use `getSafely(at:)` instead. diff --git a/FueledUtils/UIKit/DecoratingTextFieldDelegate.swift b/FueledUtils/UIKit/DecoratingTextFieldDelegate.swift index e69c1790..348a0b5e 100644 --- a/FueledUtils/UIKit/DecoratingTextFieldDelegate.swift +++ b/FueledUtils/UIKit/DecoratingTextFieldDelegate.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 UIKit import Foundation diff --git a/FueledUtils/UIKit/DimmingButton.swift b/FueledUtils/UIKit/DimmingButton.swift index 0153e0e8..08faf380 100644 --- a/FueledUtils/UIKit/DimmingButton.swift +++ b/FueledUtils/UIKit/DimmingButton.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import UIKit diff --git a/FueledUtils/UIKit/GradientView.swift b/FueledUtils/UIKit/GradientView.swift index 0ff78ce4..baae13d8 100644 --- a/FueledUtils/UIKit/GradientView.swift +++ b/FueledUtils/UIKit/GradientView.swift @@ -1,3 +1,17 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 UIKit /// diff --git a/FueledUtils/UIKit/HairlineView.swift b/FueledUtils/UIKit/HairlineView.swift index 934d0f66..3af3fdfd 100644 --- a/FueledUtils/UIKit/HairlineView.swift +++ b/FueledUtils/UIKit/HairlineView.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import UIKit diff --git a/FueledUtils/UIKit/KeyboardInsetHelper.swift b/FueledUtils/UIKit/KeyboardInsetHelper.swift index d209752b..e6ada319 100644 --- a/FueledUtils/UIKit/KeyboardInsetHelper.swift +++ b/FueledUtils/UIKit/KeyboardInsetHelper.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import UIKit diff --git a/FueledUtils/UIKit/LabelWithTitleAdjustment.swift b/FueledUtils/UIKit/LabelWithTitleAdjustment.swift index e125c45b..cae83a7a 100644 --- a/FueledUtils/UIKit/LabelWithTitleAdjustment.swift +++ b/FueledUtils/UIKit/LabelWithTitleAdjustment.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 UIKit import Foundation diff --git a/FueledUtils/UIKit/ScrollViewPage.swift b/FueledUtils/UIKit/ScrollViewPage.swift index a32ca4aa..f9ad5033 100644 --- a/FueledUtils/UIKit/ScrollViewPage.swift +++ b/FueledUtils/UIKit/ScrollViewPage.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 UIKit extension UIScrollView { diff --git a/FueledUtils/UIKit/SetRootViewController.swift b/FueledUtils/UIKit/SetRootViewController.swift index 2f37ef41..84401c4a 100644 --- a/FueledUtils/UIKit/SetRootViewController.swift +++ b/FueledUtils/UIKit/SetRootViewController.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import UIKit diff --git a/FueledUtils/UIKit/UIExtensions.swift b/FueledUtils/UIKit/UIExtensions.swift index a0d45a2a..32625e2d 100644 --- a/FueledUtils/UIKit/UIExtensions.swift +++ b/FueledUtils/UIKit/UIExtensions.swift @@ -1,18 +1,17 @@ -/* -Copyright © 2019 Fueled Digital Media, LLC +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. -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 import UIKit diff --git a/LICENSE b/LICENSE index 0c8a8002..b3950782 100644 --- a/LICENSE +++ b/LICENSE @@ -1,53 +1,201 @@ -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and -You must cause any modified files to carry prominent notices stating that You changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020, Fueled Digital Media, LLC + + 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. diff --git a/Tests/ReactiveSwift/CoalescingActionSpec.swift b/Tests/ReactiveSwift/CoalescingActionSpec.swift index cf83783b..685ac881 100644 --- a/Tests/ReactiveSwift/CoalescingActionSpec.swift +++ b/Tests/ReactiveSwift/CoalescingActionSpec.swift @@ -1,10 +1,16 @@ +// Copyright © 2020 Fueled Digital Media, LLC // -// CoalescingActionSpec.swift -// FueledUtilsTests +// 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 // -// Created by Stéphane Copin on 10/15/19. -// Copyright © 2019 Fueled. All rights reserved. +// 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 Quick import Nimble diff --git a/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift b/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift index d1e2bfb4..273ac5d8 100644 --- a/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift +++ b/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift @@ -1,10 +1,16 @@ +// Copyright © 2020 Fueled Digital Media, LLC // -// ReactiveSwiftExtensionsSpec.swift -// FueledUtilsTests +// 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 // -// Created by Stéphane Copin on 1/29/19. -// Copyright © 2019 Fueled. All rights reserved. +// 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 Quick import Nimble diff --git a/Tests/SwiftUI/EmptySpec.swift b/Tests/SwiftUI/EmptySpec.swift index db861734..3b01d398 100644 --- a/Tests/SwiftUI/EmptySpec.swift +++ b/Tests/SwiftUI/EmptySpec.swift @@ -1,10 +1,16 @@ +// Copyright © 2020 Fueled Digital Media, LLC // -// FueledUtilsTests_Combine.swift -// FueledUtilsTests-Combine +// 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 // -// Created by Stéphane Copin on 3/10/20. -// Copyright © 2020 CocoaPods. All rights reserved. +// 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 Quick import Nimble From e259d873c681427a7bdd25a0ce1ef200a709ed02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 18:05:31 -0400 Subject: [PATCH 07/89] chore(code-owners): add primary maintainers as code owners --- Tests/.github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 Tests/.github/CODEOWNERS diff --git a/Tests/.github/CODEOWNERS b/Tests/.github/CODEOWNERS new file mode 100644 index 00000000..68ba5749 --- /dev/null +++ b/Tests/.github/CODEOWNERS @@ -0,0 +1 @@ +* @stephanecopin @heymansmile @notbenoit From 0d37ba0fb8b3f3806c8b895f893e411029efbbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 10 Mar 2020 18:21:05 -0400 Subject: [PATCH 08/89] refactor(podspec): simplify podspec to have less subspecs --- FueledUtils.podspec | 25 ++-- Tests/FueledUtils.xcodeproj/project.pbxproj | 120 ++++++++++---------- Tests/Podfile.lock | 24 ++-- 3 files changed, 76 insertions(+), 93 deletions(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index d565a266..cb75ab76 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -15,10 +15,6 @@ Pod::Spec.new do |s| s.subspec 'Core' do |s| s.source_files = 'FueledUtils/Core/**/*.swift' - end - - s.subspec 'iOS8' do |s| - s.dependency 'FueledUtils/Core' s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.9' @@ -26,17 +22,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '9.0' end - s.subspec 'iOS13' do |s| - s.dependency 'FueledUtils/Core' - - s.ios.deployment_target = '13.0' - s.osx.deployment_target = '10.15' - s.watchos.deployment_target = '6.0' - s.tvos.deployment_target = '13.0' - end - s.subspec 'ReactiveSwift' do |s| - s.dependency 'FueledUtils/iOS8' s.dependency 'ReactiveSwift', '~> 6.0' s.dependency 'ReactiveCocoa', '~> 10.0' @@ -44,19 +30,22 @@ Pod::Spec.new do |s| end s.subspec 'UIKit' do |s| - s.dependency 'FueledUtils/iOS8' - s.source_files = 'FueledUtils/UIKit/**/*.swift' + s.dependency 'FueledUtils/Core' + s.ios.source_files = 'FueledUtils/UIKit/**/*.swift' end s.subspec 'ReactiveSwiftUIKit' do |s| s.dependency 'FueledUtils/ReactiveSwift' s.dependency 'FueledUtils/UIKit' - s.source_files = 'FueledUtils/ReactiveSwiftUIKit/**/*.swift' + s.ios.source_files = 'FueledUtils/ReactiveSwiftUIKit/**/*.swift' end s.subspec 'Combine' do |s| - s.dependency 'FueledUtils/iOS13' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + s.watchos.deployment_target = '6.0' + s.tvos.deployment_target = '13.0' s.source_files = 'FueledUtils/Combine/**/*.swift' end diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index c20c30c2..f09486a3 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -7,22 +7,22 @@ objects = { /* Begin PBXBuildFile section */ - 2226E30A096757B47E86AB53 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; - C2A10C8364626483B976411E /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; + CBAC4FA461D4F6B2536EF7AB /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; F463C74724183914000A0B29 /* EmptySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C74624183914000A0B29 /* EmptySpec.swift */; }; + FDC158127A32ACE063DE046F /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; - 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; + 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-ReactiveSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; - BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; + 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; + ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; - D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; + D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; @@ -37,7 +37,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2226E30A096757B47E86AB53 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, + FDC158127A32ACE063DE046F /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -45,29 +45,20 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C2A10C8364626483B976411E /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, + CBAC4FA461D4F6B2536EF7AB /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 02A127E0260A9C408A650AFD /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5F6FE40D407406C7CE909FA8 /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, - D5900E02D753E34472A95FDB /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 29F7E676D0DA9F23C4893ECA /* Pods */ = { isa = PBXGroup; children = ( - 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, - BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, - D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, - 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, + D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */, + ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, + 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, + 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -81,7 +72,7 @@ F463C73E241835EA000A0B29 /* Supporting Files */, 607FACD11AFB9204008FA782 /* Products */, 29F7E676D0DA9F23C4893ECA /* Pods */, - 02A127E0260A9C408A650AFD /* Frameworks */, + 8CF3F11B25F743ED68711CBC /* Frameworks */, ); sourceTree = ""; }; @@ -104,6 +95,15 @@ name = "Podspec Metadata"; sourceTree = ""; }; + 8CF3F11B25F743ED68711CBC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, + 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; F463C739241835DD000A0B29 /* ReactiveSwift */ = { isa = PBXGroup; children = ( @@ -136,11 +136,11 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */; buildPhases = ( - B5C5ECD5F611B11872D34C4A /* [CP] Check Pods Manifest.lock */, + 7A704080E54E9F7FB6D2806D /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, 607FACE21AFB9204008FA782 /* Frameworks */, 607FACE31AFB9204008FA782 /* Resources */, - 8C7652B0CEBBDD052B99C924 /* [CP] Embed Pods Frameworks */, + 41CB9636F71B84882C2E6A45 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -155,11 +155,11 @@ isa = PBXNativeTarget; buildConfigurationList = F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */; buildPhases = ( - AC6F6AE7495CB52DF3C2EB32 /* [CP] Check Pods Manifest.lock */, + C83C49377CB4DCCA96B27625 /* [CP] Check Pods Manifest.lock */, F463C74024183913000A0B29 /* Sources */, F463C74124183913000A0B29 /* Frameworks */, F463C74224183913000A0B29 /* Resources */, - E807AE5D00D79221217A1841 /* [CP] Embed Pods Frameworks */, + 3EB0A0B44D7FAD0AAD39085B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -228,14 +228,36 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 8C7652B0CEBBDD052B99C924 /* [CP] Embed Pods Frameworks */ = { + 3EB0A0B44D7FAD0AAD39085B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FueledUtils-Combine-CombineOperators-Core-SwiftUI-UIKit/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 41CB9636F71B84882C2E6A45 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-Core-ReactiveSwift-ReactiveSwiftUIKit-UIKit-iOS8/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/FueledUtils-Core-ReactiveSwift-ReactiveSwiftUIKit-UIKit/FueledUtils.framework", "${BUILT_PRODUCTS_DIR}/Nimble-iOS8.0/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick-iOS8.0/Quick.framework", "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", @@ -254,7 +276,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - AC6F6AE7495CB52DF3C2EB32 /* [CP] Check Pods Manifest.lock */ = { + 7A704080E54E9F7FB6D2806D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -269,14 +291,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B5C5ECD5F611B11872D34C4A /* [CP] Check Pods Manifest.lock */ = { + C83C49377CB4DCCA96B27625 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -291,35 +313,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E807AE5D00D79221217A1841 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-2d777442/FueledUtils.framework", - "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -450,7 +450,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 955B6BB2E31ED847762F7DCD /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; + baseConfigurationReference = D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -471,7 +471,7 @@ }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BA1CA64948C05D31684C23A9 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; + baseConfigurationReference = ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -488,7 +488,7 @@ }; F463C74A24183914000A0B29 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D63C4DFA59B857AA08C9A57F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; + baseConfigurationReference = 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -518,7 +518,7 @@ }; F463C74B24183914000A0B29 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 409C8FBD4FD224217FD8DE2C /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; + baseConfigurationReference = 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index d5bcf641..30f5680c 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,25 +1,19 @@ PODS: - - FueledUtils/Combine (2.0.3): - - FueledUtils/iOS13 - - FueledUtils/CombineOperators (2.0.3): + - FueledUtils/Combine (3.0) + - FueledUtils/CombineOperators (3.0): - FueledUtils/Combine - - FueledUtils/Core (2.0.3) - - FueledUtils/iOS13 (2.0.3): - - FueledUtils/Core - - FueledUtils/iOS8 (2.0.3): - - FueledUtils/Core - - FueledUtils/ReactiveSwift (2.0.3): - - FueledUtils/iOS8 + - FueledUtils/Core (3.0) + - FueledUtils/ReactiveSwift (3.0): - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) - - FueledUtils/ReactiveSwiftUIKit (2.0.3): + - FueledUtils/ReactiveSwiftUIKit (3.0): - FueledUtils/ReactiveSwift - FueledUtils/UIKit - - FueledUtils/SwiftUI (2.0.3): + - FueledUtils/SwiftUI (3.0): - FueledUtils/Combine - FueledUtils/Core - - FueledUtils/UIKit (2.0.3): - - FueledUtils/iOS8 + - FueledUtils/UIKit (3.0): + - FueledUtils/Core - Nimble (8.0.5) - Quick (2.2.0) - ReactiveCocoa (10.2.0): @@ -46,7 +40,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: 481701ad929bc05aebf62c0d8eac3c5aeaeac963 + FueledUtils: cb348b77e24486d5a8c2dc3d7b65bdb2cdff277d Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 From 0418857c61b7d4d45dd8ab232c82450785cec8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 13 Mar 2020 13:36:09 -0400 Subject: [PATCH 09/89] chore(tests/code-coverage): enable code coverage --- .../FueledUtilsTests-SwiftUI.xcscheme | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme index 0ce61329..909350b9 100644 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme @@ -10,7 +10,25 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + + + From 5b42f664cac286eef5d572b422b57c55bcad1e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 19 Mar 2020 15:21:14 -0400 Subject: [PATCH 10/89] feat(swiftui/frame-preference-key): add TagType to FramePreferenceKey --- FueledUtils/SwiftUI/FramePreferenceKey.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/FueledUtils/SwiftUI/FramePreferenceKey.swift b/FueledUtils/SwiftUI/FramePreferenceKey.swift index b50c8a73..b8273119 100644 --- a/FueledUtils/SwiftUI/FramePreferenceKey.swift +++ b/FueledUtils/SwiftUI/FramePreferenceKey.swift @@ -14,8 +14,14 @@ import SwiftUI -public struct FramePreferenceKey: PreferenceKey { - public static var defaultValue: CGRect = .zero +/// +/// Used to retrieve the frame of a view through a preference key. +/// `TagType` is used to uniquely identify the view using the preference key. +/// +public struct FramePreferenceKey: PreferenceKey { + public static var defaultValue: CGRect { + .zero + } public static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() From 95ee6638af051ad489495afce20f87265a543e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 24 Mar 2020 10:20:34 -0400 Subject: [PATCH 11/89] fix(combine): fix combine that were using CombineOperators subspec by mistake --- FueledUtils/Combine/Action.swift | 7 ++++--- FueledUtils/Combine/ObservableObjectExtensions.swift | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 561bb7b7..77cc6e15 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -97,12 +97,13 @@ public final class Action { .eraseToAnyPublisher() } - self~\.isEnabled <~ Publishers.CombineLatest( + Publishers.CombineLatest( isEnabled, self.$isExecuting ) - .map { $0 && !$1 } - >>> self.cancellables + .map { $0 && !$1 } + .assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) } public func apply(_ input: Input) -> AnyPublisher> { diff --git a/FueledUtils/Combine/ObservableObjectExtensions.swift b/FueledUtils/Combine/ObservableObjectExtensions.swift index 39ec2022..74d8eb49 100644 --- a/FueledUtils/Combine/ObservableObjectExtensions.swift +++ b/FueledUtils/Combine/ObservableObjectExtensions.swift @@ -65,7 +65,7 @@ extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObj cancellables = Set() objects.forEach { object in object.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() } - >>> cancellables + .store(in: &cancellables) } return .unlimited } From 3be8955453b0036738b55d2fdde37dad5cbe6e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 26 Mar 2020 17:52:56 -0400 Subject: [PATCH 12/89] chore(action): remove SwiftUI import from Action --- FueledUtils/Combine/Action.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 77cc6e15..716284b1 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -13,7 +13,6 @@ // limitations under the License. import Combine -import SwiftUI public protocol ActionErrorProtocol { associatedtype InnerError: Swift.Error From 8224e177df3621a90d4b3147ae84854558616466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 26 Mar 2020 17:53:14 -0400 Subject: [PATCH 13/89] fix(action): fix issue where the isExecuting flag would never reset when an error is received --- FueledUtils/Combine/Action.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 716284b1..17336071 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -57,7 +57,8 @@ public final class Action { ) where EnabledIfPublisher.Output == Bool, EnabledIfPublisher.Failure == Never, ExecutePublisher.Output == Output, - ExecutePublisher.Failure == Failure { + ExecutePublisher.Failure == Failure + { let values = PassthroughSubject() let errors = PassthroughSubject() @@ -82,11 +83,12 @@ public final class Action { values.send(value) }, receiveCompletion: { [weak action] completion in + isExecutingLock.lock() + action?.isExecuting = false + isExecutingLock.unlock() switch completion { case .finished: - isExecutingLock.lock() - action?.isExecuting = false - isExecutingLock.unlock() + break case .failure(let error): errors.send(error) } From 8baff7229b63593a6adc51ad364d5b9b9c8ec590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 9 Apr 2020 14:32:50 -0400 Subject: [PATCH 14/89] feat(action): add constant factory method to Action, allowing to mock Action --- FueledUtils/Combine/Action.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 17336071..cd623fa3 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -129,3 +129,12 @@ extension Publisher where Failure: ActionErrorProtocol { .eraseToAnyPublisher() } } +extension Action { + public static func constant(_ value: Output) -> Action { + self.constant(inputType: Input.self, value: value) + } + + public static func constant(inputType: Input.Type, value: Output) -> Action { + Action { _ in Just(value).setFailureType(to: Failure.self) } + } +} From fd11e2ab981b0131031e9519ac67b51a8f366cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 9 Apr 2020 14:33:03 -0400 Subject: [PATCH 15/89] feat(action): add map methods to Action --- FueledUtils/Combine/Action.swift | 89 ++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index cd623fa3..22847ae0 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -34,6 +34,15 @@ extension ActionError: ActionErrorProtocol { } } +extension ActionErrorProtocol { + public func map(_ mapper: (InnerError) -> NewError) -> ActionError { + if let innerError = self.innerError { + return .failure(mapper(innerError)) + } + return .disabled + } +} + public final class Action { @Published public private(set) var isExecuting: Bool = false @Published public private(set) var isEnabled: Bool = false @@ -41,9 +50,9 @@ public final class Action { public let values: AnyPublisher public let errors: AnyPublisher - private let execute: (Action, Input) -> AnyPublisher> + fileprivate let execute: (Action, Input) -> AnyPublisher> - private var cancellables = Set([]) + fileprivate var cancellables = Set([]) public convenience init( execute: @escaping (Input) -> ExecutePublisher @@ -54,7 +63,8 @@ public final class Action { public init( enabledIf isEnabled: EnabledIfPublisher, execute: @escaping (Input) -> ExecutePublisher - ) where EnabledIfPublisher.Output == Bool, + ) where + EnabledIfPublisher.Output == Bool, EnabledIfPublisher.Failure == Never, ExecutePublisher.Output == Output, ExecutePublisher.Failure == Failure @@ -107,6 +117,28 @@ public final class Action { .store(in: &self.cancellables) } + fileprivate init( + enabledIf isEnabled: EnabledIfPublisher, + values: AnyPublisher, + errors: AnyPublisher, + execute: @escaping (Action, Input) -> AnyPublisher> + ) where + EnabledIfPublisher.Output == Bool, + EnabledIfPublisher.Failure == Never + { + self.values = values + self.errors = errors + self.execute = execute + + Publishers.CombineLatest( + isEnabled, + self.$isExecuting + ) + .map { $0 && !$1 } + .assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + } + public func apply(_ input: Input) -> AnyPublisher> { self.execute(self, input) } @@ -129,6 +161,7 @@ extension Publisher where Failure: ActionErrorProtocol { .eraseToAnyPublisher() } } + extension Action { public static func constant(_ value: Output) -> Action { self.constant(inputType: Input.self, value: value) @@ -137,4 +170,54 @@ extension Action { public static func constant(inputType: Input.Type, value: Output) -> Action { Action { _ in Just(value).setFailureType(to: Failure.self) } } + + // Please note that the actions created with the `mapXxx` family are interweaved together - starting one + // will update the other, and vice versa. + // For example, on use case is to type-erase an Action. + public func mapInput(_ mapper: @escaping (NewInput) -> Input) -> Action { + self.mapAll( + mapInput: mapper, + map: { $0 }, + mapError: { $0 } + ) + } + + public func map(_ mapper: @escaping (Output) -> NewOutput) -> Action { + self.mapAll( + mapInput: { $0 }, + map: mapper, + mapError: { $0 } + ) + } + + public func mapError(_ mapper: @escaping (Failure) -> NewFailure) -> Action { + self.mapAll( + mapInput: { $0 }, + map: { $0 }, + mapError: mapper + ) + } + + public func mapAll( + mapInput: @escaping (NewInput) -> (Input), + map: @escaping (Output) -> (NewOutput), + mapError: @escaping (Failure) -> (NewFailure) + ) -> Action { + let action = Action( + enabledIf: self.$isEnabled, + values: self.values.map(map).eraseToAnyPublisher(), + errors: self.errors.map(mapError).eraseToAnyPublisher() + ) { (action, input) -> AnyPublisher> in + return self.execute(self, mapInput(input)) + .map(map) + .mapError { $0.map { mapError($0) } } + .eraseToAnyPublisher() + } + + self.$isExecuting + .assign(to: \.isExecuting, withoutRetaining: action) + .store(in: &action.cancellables) + + return action + } } From c87d8aa54014b15937147979b3bd64d0074812f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 9 Apr 2020 14:34:49 -0400 Subject: [PATCH 16/89] feat(combine): add more misc helpers for Combine --- .../Combine/CurrentValuePublisher.swift | 54 +++++++++++++++++++ .../Combine/Publisher+IgnoreLoading.swift | 49 +++++++++++++++++ .../Combine/Publisher+IgnoreRepeats.swift | 34 ++++++++++++ FueledUtils/Combine/Subject+SendResult.swift | 21 ++++++++ .../Subscriber+EraseToAnySubscriber.swift | 15 ++++++ .../CombineOperators+Optional.swift | 40 ++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 FueledUtils/Combine/CurrentValuePublisher.swift create mode 100644 FueledUtils/Combine/Publisher+IgnoreLoading.swift create mode 100644 FueledUtils/Combine/Publisher+IgnoreRepeats.swift create mode 100644 FueledUtils/Combine/Subject+SendResult.swift create mode 100644 FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift create mode 100644 FueledUtils/CombineOperators/CombineOperators+Optional.swift diff --git a/FueledUtils/Combine/CurrentValuePublisher.swift b/FueledUtils/Combine/CurrentValuePublisher.swift new file mode 100644 index 00000000..5d2e5551 --- /dev/null +++ b/FueledUtils/Combine/CurrentValuePublisher.swift @@ -0,0 +1,54 @@ +// +// CurrentValuePublisher.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/24/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +/// +/// A type-erasing current value publisher. +/// +/// Use an `AnyCurrentValuePublisher` to wrap an existing current value publisher whose details you don’t want to expose. +/// For example, this is useful if you want to use a `CurrentValueSubject` internally, but don't want to expose the setter/its send() method +/// +public struct AnyCurrentValuePublisher: CurrentValuePublisher { + private let valueGetter: () -> Output + private let receiveSubcriberClosure: (AnySubscriber) -> Void + + public var value: Output { + self.valueGetter() + } + + public init(_ value: Output) { + self.valueGetter = { value } + self.receiveSubcriberClosure = { _ = $0.receive(value) } + } + + public init(_ publisher: CurrentValuePublisher) where CurrentValuePublisher.Output == Output, CurrentValuePublisher.Failure == Failure { + self.valueGetter = { publisher.value } + self.receiveSubcriberClosure = { publisher.receive(subscriber: $0) } + } + + public func receive(subscriber: Subscriber) where Subscriber.Input == Output, Subscriber.Failure == Failure { + self.receiveSubcriberClosure(subscriber.eraseToAnySubscriber()) + } +} + +extension CurrentValuePublisher { + public func eraseToAnyCurrentValuePublisher() -> AnyCurrentValuePublisher { + AnyCurrentValuePublisher(self) + } +} + +/// +/// A publisher that also stores the last value it sent +/// +public protocol CurrentValuePublisher: Publisher { + var value: Output { get } +} + +extension CurrentValueSubject: CurrentValuePublisher { +} diff --git a/FueledUtils/Combine/Publisher+IgnoreLoading.swift b/FueledUtils/Combine/Publisher+IgnoreLoading.swift new file mode 100644 index 00000000..47c5dbf6 --- /dev/null +++ b/FueledUtils/Combine/Publisher+IgnoreLoading.swift @@ -0,0 +1,49 @@ +// +// Publisher+IgnoreLoading.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/31/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine +import FueledUtils + +public protocol TransferStateProtocol { + associatedtype Progress + associatedtype Value + + var progress: Progress? { get } + var value: Value? { get } +} + +extension TransferState: TransferStateProtocol { + public var progress: Progress? { + if case .loading(let progress) = self { + return progress + } + return nil + } + + public var value: Value? { + if case .finished(let value) = self { + return value + } + return nil + } +} + +extension Publisher where Output: TransferStateProtocol { + public func ignoreLoading() -> AnyPublisher { + self.flatMap { transferState -> AnyPublisher in + guard let value = transferState.value else { + return Empty(completeImmediately: true) + .eraseToAnyPublisher() + } + return Just(value) + .setFailureType(to: Failure.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift new file mode 100644 index 00000000..210f250a --- /dev/null +++ b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift @@ -0,0 +1,34 @@ +// +// Publisher+IgnoreRepeats.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/26/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine +import FueledUtils + +extension Publisher { + public func ignoreRepeats(isEqual: @escaping (Output, Output) -> Bool) -> AnyPublisher { + self.map { Optional($0) } + .combinePrevious(nil) + .flatMap { previous, current -> AnyPublisher in + let current = current! + if previous.map({ isEqual($0, current) }) ?? false { + return Empty(completeImmediately: false) + .eraseToAnyPublisher() + } + return Just(current) + .setFailureType(to: Failure.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} + +extension Publisher where Output: Equatable { + public func ignoreRepeats() -> AnyPublisher { + self.ignoreRepeats(isEqual: ==) + } +} diff --git a/FueledUtils/Combine/Subject+SendResult.swift b/FueledUtils/Combine/Subject+SendResult.swift new file mode 100644 index 00000000..9db2a9c8 --- /dev/null +++ b/FueledUtils/Combine/Subject+SendResult.swift @@ -0,0 +1,21 @@ +// +// Subject+SendResult.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/31/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Subject { + func send(result: Result) { + switch result { + case .failure(let error): + self.send(completion: .failure(error)) + case .success(let value): + self.send(value) + self.send(completion: .finished) + } + } +} diff --git a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift new file mode 100644 index 00000000..fc66b0db --- /dev/null +++ b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift @@ -0,0 +1,15 @@ +// +// Subscriber+EraseToAnySubscriber.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/24/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine + +extension Subscriber { + public func eraseToAnySubscriber() -> AnySubscriber { + AnySubscriber(self) + } +} diff --git a/FueledUtils/CombineOperators/CombineOperators+Optional.swift b/FueledUtils/CombineOperators/CombineOperators+Optional.swift new file mode 100644 index 00000000..a1698565 --- /dev/null +++ b/FueledUtils/CombineOperators/CombineOperators+Optional.swift @@ -0,0 +1,40 @@ +// +// CombineOperators+Optional.swift +// RPMHelpers +// +// Created by Stéphane Copin on 3/26/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import Combine +import FueledUtils + +public func >>> (lhs: AnyCancellable?, rhs: inout CancellableCollection) where CancellableCollection.Element == AnyCancellable { + lhs?.store(in: &rhs) +} + +public func >>> (lhs: AnyCancellable?, rhs: inout Set) { + lhs?.store(in: &rhs) +} + +public func >>> (lhs: AnyCancellable, rhs: inout CancellableCollection?) where CancellableCollection.Element == AnyCancellable { + rhs?.append(lhs) +} + +public func >>> (lhs: AnyCancellable, rhs: inout Set?) { + rhs?.insert(lhs) +} + +public func >>> (lhs: AnyCancellable?, rhs: inout CancellableCollection?) where CancellableCollection.Element == AnyCancellable { + guard let lhs = lhs else { + return + } + lhs >>> rhs +} + +public func >>> (lhs: AnyCancellable?, rhs: inout Set?) { + guard let lhs = lhs else { + return + } + lhs >>> rhs +} From bc5259020dd08000a836532c502311821609a9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 13 Apr 2020 14:40:38 -0400 Subject: [PATCH 17/89] chore(combine/subject/send-result): fix API issue where a public was missing --- FueledUtils/Combine/Subject+SendResult.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils/Combine/Subject+SendResult.swift b/FueledUtils/Combine/Subject+SendResult.swift index 9db2a9c8..1c999d72 100644 --- a/FueledUtils/Combine/Subject+SendResult.swift +++ b/FueledUtils/Combine/Subject+SendResult.swift @@ -9,7 +9,7 @@ import Combine extension Subject { - func send(result: Result) { + public func send(result: Result) { switch result { case .failure(let error): self.send(completion: .failure(error)) From 620893ec8f12f2093c64edaecd433855be50392f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 13 Apr 2020 14:42:19 -0400 Subject: [PATCH 18/89] feat(swift-ui/blur): add background blur view, allowing to blur views behind itself --- FueledUtils/SwiftUI/BackgroundBlur.swift | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 FueledUtils/SwiftUI/BackgroundBlur.swift diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift new file mode 100644 index 00000000..5dbaaee4 --- /dev/null +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -0,0 +1,31 @@ +// +// BackgroundBlur.swift +// RPMHelpers +// +// Created by Stéphane Copin on 4/10/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +public struct BlurView: UIViewRepresentable { + public let style: UIBlurEffect.Style + + public func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: self.style)) + } + + public func updateUIView(_ visualEffectView: UIVisualEffectView, context: Context) { + visualEffectView.effect = UIBlurEffect(style: self.style) + } +} + +extension View { + public func backgroundBlur(style: UIBlurEffect.Style, color: Color? = nil) -> some View { + ZStack { + color + BlurView(style: style) + self + } + } +} From 8fd27768460ea8534de68ecac07001fe765d0db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 13 Apr 2020 14:42:53 -0400 Subject: [PATCH 19/89] feat(swift-ui/edge-insets): add additional initializers for EdgeInsets that mimics the overloads of .padding() --- FueledUtils/SwiftUI/EdgeInsets+Helpers.swift | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 FueledUtils/SwiftUI/EdgeInsets+Helpers.swift diff --git a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift new file mode 100644 index 00000000..912159c7 --- /dev/null +++ b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift @@ -0,0 +1,28 @@ +// +// EdgeInsets+Zero.swift +// RPMHelpers +// +// Created by Stéphane Copin on 4/10/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +extension EdgeInsets { + public static var zero: EdgeInsets { + EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 0.0) + } + + public init(_ length: CGFloat) { + self.init(top: length, leading: length, bottom: length, trailing: length) + } + + public init(_ edges: Edge.Set, _ length: CGFloat) { + self.init( + top: edges.contains(.top) ? length : 0.0, + leading: edges.contains(.leading) ? length : 0.0, + bottom: edges.contains(.bottom) ? length : 0.0, + trailing: edges.contains(.trailing) ? length : 0.0 + ) + } +} From a39e801e2b22c8cc2ffdb22dd3d705aa236e53ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 13 Apr 2020 14:43:20 -0400 Subject: [PATCH 20/89] feat(swift-ui/for-each-with-index): replace the existing helper for creating a collection with indices with ForEachWithIndex --- FueledUtils/SwiftUI/ForEach+IndexInfo.swift | 67 --------------------- FueledUtils/SwiftUI/ForEachWithIndex.swift | 60 ++++++++++++++++++ 2 files changed, 60 insertions(+), 67 deletions(-) delete mode 100644 FueledUtils/SwiftUI/ForEach+IndexInfo.swift create mode 100644 FueledUtils/SwiftUI/ForEachWithIndex.swift diff --git a/FueledUtils/SwiftUI/ForEach+IndexInfo.swift b/FueledUtils/SwiftUI/ForEach+IndexInfo.swift deleted file mode 100644 index ec7f8795..00000000 --- a/FueledUtils/SwiftUI/ForEach+IndexInfo.swift +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2020, Fueled Digital Media, LLC -// -// 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 SwiftUI - -public struct IndexInfo: Identifiable { - public let index: Index - public let id: ID - public let object: Object - - init(index: Index, object: Object, id: KeyPath) { - self.index = index - self.id = object[keyPath: id] - self.object = object - } -} - -extension IndexInfo where Object: Identifiable, ID == Object.ID { - public init(index: Index, object: Object) { - self.init( - index: index, - object: object, - id: \.self.id - ) - } -} - -extension IndexInfo where Object: Identifiable, ID == Object { - public init(index: Index, object: Object) { - self.init( - index: index, - object: object, - id: \.self - ) - } -} - -extension IndexInfo where Object: Hashable, ID == Object { - public init(index: Index, object: Object) { - self.init( - index: index, - object: object, - id: \.self - ) - } -} - -extension RandomAccessCollection { - public func withIndices(id: KeyPath) -> [IndexInfo] { - zip(self.indices, self).map { IndexInfo(index: $0, object: $1, id: id) } - } - - public func withIndices(id: KeyPath) -> [IndexInfo] { - zip(self.indices, self).map { IndexInfo(index: $0, object: $1, id: id) } - } -} diff --git a/FueledUtils/SwiftUI/ForEachWithIndex.swift b/FueledUtils/SwiftUI/ForEachWithIndex.swift new file mode 100644 index 00000000..ebafc1c2 --- /dev/null +++ b/FueledUtils/SwiftUI/ForEachWithIndex.swift @@ -0,0 +1,60 @@ +// +// ForEachWithIndex.swift +// RPMHelpers +// +// Created by Stéphane Copin on 4/10/20. +// Copyright © 2020 Fueled. All rights reserved. +// + +import SwiftUI + +public struct ForEachWithIndex: View { + var data: Data + var id: KeyPath + var content: (_ index: Data.Index, _ element: Data.Element) -> Content + + public init(_ data: Data, id: KeyPath, content: @escaping (_ index: Data.Index, _ element: Data.Element) -> Content) { + self.data = data + self.id = id + self.content = content + } + + public var body: some View { + ForEach( + zip(self.data.indices, self.data).map { index, element in + IndexInfo( + index: index, + id: self.id, + element: element + ) + }, + id: \.elementID + ) { indexInfo in + self.content(indexInfo.index, indexInfo.element) + } + } +} + +extension ForEachWithIndex where ID == Data.Element.ID, Content: View, Data.Element: Identifiable { + public init(_ data: Data, @ViewBuilder content: @escaping (_ index: Data.Index, _ element: Data.Element) -> Content) { + self.init(data, id: \.id, content: content) + } +} + +private struct IndexInfo: Hashable { + let index: Index + let id: KeyPath + let element: Element + + var elementID: ID { + self.element[keyPath: self.id] + } + + static func == (_ lhs: IndexInfo, _ rhs: IndexInfo) -> Bool { + lhs.elementID == rhs.elementID + } + + func hash(into hasher: inout Hasher) { + self.elementID.hash(into: &hasher) + } +} From 54ead6e7834aa460510d0b8274f5138165a36b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 13 Apr 2020 14:45:25 -0400 Subject: [PATCH 21/89] chore(license): update license text on top of each source files --- FueledUtils/Combine/CurrentValuePublisher.swift | 14 ++++++++++---- FueledUtils/Combine/Publisher+IgnoreLoading.swift | 14 ++++++++++---- FueledUtils/Combine/Publisher+IgnoreRepeats.swift | 14 ++++++++++---- FueledUtils/Combine/Subject+SendResult.swift | 14 ++++++++++---- .../Combine/Subscriber+EraseToAnySubscriber.swift | 14 ++++++++++---- .../CombineOperators+Optional.swift | 14 ++++++++++---- FueledUtils/SwiftUI/BackgroundBlur.swift | 14 ++++++++++---- FueledUtils/SwiftUI/EdgeInsets+Helpers.swift | 14 ++++++++++---- FueledUtils/SwiftUI/ForEachWithIndex.swift | 14 ++++++++++---- 9 files changed, 90 insertions(+), 36 deletions(-) diff --git a/FueledUtils/Combine/CurrentValuePublisher.swift b/FueledUtils/Combine/CurrentValuePublisher.swift index 5d2e5551..924328f2 100644 --- a/FueledUtils/Combine/CurrentValuePublisher.swift +++ b/FueledUtils/Combine/CurrentValuePublisher.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// CurrentValuePublisher.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/24/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/Publisher+IgnoreLoading.swift b/FueledUtils/Combine/Publisher+IgnoreLoading.swift index 47c5dbf6..5b327c71 100644 --- a/FueledUtils/Combine/Publisher+IgnoreLoading.swift +++ b/FueledUtils/Combine/Publisher+IgnoreLoading.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Publisher+IgnoreLoading.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/31/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine import FueledUtils diff --git a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift index 210f250a..a5fbdccf 100644 --- a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift +++ b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Publisher+IgnoreRepeats.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/26/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine import FueledUtils diff --git a/FueledUtils/Combine/Subject+SendResult.swift b/FueledUtils/Combine/Subject+SendResult.swift index 1c999d72..b17f4bd3 100644 --- a/FueledUtils/Combine/Subject+SendResult.swift +++ b/FueledUtils/Combine/Subject+SendResult.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Subject+SendResult.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/31/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift index fc66b0db..fbd14258 100644 --- a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift +++ b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Subscriber+EraseToAnySubscriber.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/24/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine diff --git a/FueledUtils/CombineOperators/CombineOperators+Optional.swift b/FueledUtils/CombineOperators/CombineOperators+Optional.swift index a1698565..8384f7a6 100644 --- a/FueledUtils/CombineOperators/CombineOperators+Optional.swift +++ b/FueledUtils/CombineOperators/CombineOperators+Optional.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// CombineOperators+Optional.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 3/26/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 Combine import FueledUtils diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index 5dbaaee4..6c9a551c 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// BackgroundBlur.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 4/10/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift index 912159c7..213882af 100644 --- a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift +++ b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// EdgeInsets+Zero.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 4/10/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI diff --git a/FueledUtils/SwiftUI/ForEachWithIndex.swift b/FueledUtils/SwiftUI/ForEachWithIndex.swift index ebafc1c2..95b6f732 100644 --- a/FueledUtils/SwiftUI/ForEachWithIndex.swift +++ b/FueledUtils/SwiftUI/ForEachWithIndex.swift @@ -1,10 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// ForEachWithIndex.swift -// RPMHelpers +// 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 // -// Created by Stéphane Copin on 4/10/20. -// Copyright © 2020 Fueled. All rights reserved. +// 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 SwiftUI From b5761ec272476bb9376e76f493633e81782b67e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 12 May 2020 14:40:22 -0400 Subject: [PATCH 22/89] feat(combine-reactiveswift): add basic reactive swift from/to combine bridge, and update internal code to allow using all subspecs at the same time --- FueledUtils.podspec | 22 +- FueledUtils/Combine/Action.swift | 29 --- FueledUtils/Combine/ActionError.swift | 36 +++ .../Combine+ReactiveSwift.swift | 54 +++++ .../ReactiveSwift+Combine.swift | 213 ++++++++++++++++++ .../ReactiveCommon/ActionErrorProtocol.swift | 39 ++++ .../ActionError+ActionErrorProtocol.swift | 27 +++ .../ReactiveSwift/InputCoalescingAction.swift | 13 +- FueledUtils/ReactiveSwift/LoadingState.swift | 2 +- ...col.swift => ReactiveActionProtocol.swift} | 45 +--- .../ReactiveSwiftExtensions.swift | 2 +- Tests/FueledUtils.xcodeproj/project.pbxproj | 12 +- Tests/Podfile | 4 +- Tests/Podfile.lock | 30 +-- .../ReactiveSwift/CoalescingActionSpec.swift | 2 +- Tests/SwiftUI/EmptySpec.swift | 1 + 16 files changed, 423 insertions(+), 108 deletions(-) create mode 100644 FueledUtils/Combine/ActionError.swift create mode 100644 FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift create mode 100644 FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift create mode 100644 FueledUtils/ReactiveCommon/ActionErrorProtocol.swift create mode 100644 FueledUtils/ReactiveSwift/ActionError+ActionErrorProtocol.swift rename FueledUtils/ReactiveSwift/{ActionProtocol.swift => ReactiveActionProtocol.swift} (75%) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index cb75ab76..5eb1a523 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'FueledUtils' - s.version = '3.0' + s.version = '3.0-alpha1' s.summary = 'A collection of utilities used at Fueled' s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' s.swift_version = '5' @@ -14,15 +14,22 @@ Pod::Spec.new do |s| s.documentation_url = 'https://cdn.rawgit.com/Fueled/ios-utilities/master/docs/index.html' s.subspec 'Core' do |s| - s.source_files = 'FueledUtils/Core/**/*.swift' - s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.9' s.watchos.deployment_target = '2.0' s.tvos.deployment_target = '9.0' + + s.source_files = 'FueledUtils/Core/**/*.swift' + end + + s.subspec 'ReactiveCommon' do |s| + s.source_files = 'FueledUtils/ReactiveCommon/**/*.swift' + + s.dependency 'FueledUtils/Core' end s.subspec 'ReactiveSwift' do |s| + s.dependency 'FueledUtils/ReactiveCommon' s.dependency 'ReactiveSwift', '~> 6.0' s.dependency 'ReactiveCocoa', '~> 10.0' @@ -47,6 +54,8 @@ Pod::Spec.new do |s| s.watchos.deployment_target = '6.0' s.tvos.deployment_target = '13.0' + s.dependency 'FueledUtils/ReactiveCommon' + s.source_files = 'FueledUtils/Combine/**/*.swift' end @@ -63,6 +72,13 @@ Pod::Spec.new do |s| s.source_files = 'FueledUtils/SwiftUI/**/*.swift' end + s.subspec 'ReactiveCombineBridge' do |s| + s.dependency 'FueledUtils/ReactiveSwift' + s.dependency 'FueledUtils/CombineOperators' + + s.ios.source_files = 'FueledUtils/ReactiveCombineBridge/**/*.swift' + end + s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] s.ios.exclude_files = ['FueledUtils/FueledUtils.h'] s.watchos.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 22847ae0..022bca3f 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -14,35 +14,6 @@ import Combine -public protocol ActionErrorProtocol { - associatedtype InnerError: Swift.Error - - var innerError: InnerError? { get } -} - -public enum ActionError: Swift.Error { - case disabled - case failure(Error) -} - -extension ActionError: ActionErrorProtocol { - public var innerError: Error? { - if case .failure(let error) = self { - return error - } - return nil - } -} - -extension ActionErrorProtocol { - public func map(_ mapper: (InnerError) -> NewError) -> ActionError { - if let innerError = self.innerError { - return .failure(mapper(innerError)) - } - return .disabled - } -} - public final class Action { @Published public private(set) var isExecuting: Bool = false @Published public private(set) var isEnabled: Bool = false diff --git a/FueledUtils/Combine/ActionError.swift b/FueledUtils/Combine/ActionError.swift new file mode 100644 index 00000000..fbfe555e --- /dev/null +++ b/FueledUtils/Combine/ActionError.swift @@ -0,0 +1,36 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. + +public enum ActionError: Swift.Error { + case disabled + case failure(Error) +} + +extension ActionError: ActionErrorProtocol { + public var innerError: Error? { + if case .failure(let error) = self { + return error + } + return nil + } +} + +extension ActionError { + public func map(_ mapper: (InnerError) -> NewError) -> ActionError { + if let innerError = self.innerError { + return .failure(mapper(innerError)) + } + return .disabled + } +} diff --git a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift new file mode 100644 index 00000000..5faa596c --- /dev/null +++ b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift @@ -0,0 +1,54 @@ +// +// Combine+ReactiveSwift.swift +// FueledUtils-c6ea1015 +// +// Created by Stéphane Copin on 5/12/20. +// + +import Combine +import ReactiveSwift + +extension Publisher { + public var producer: SignalProducer { + SignalProducer { observer, lifetime in + lifetime += self.sink( + receiveCompletion: { completion in + switch completion { + case .finished: + observer.sendCompleted() + case .failure(let error): + observer.send(error: error) + } + }, + receiveValue: { value in + observer.send(value: value) + } + ) + } + } +} + +extension Lifetime { + @discardableResult + public static func += (lhs: Lifetime, rhs: Cancellable?) -> Disposable? { + rhs.flatMap { lhs.observeEnded($0.cancel) } + } +} + +extension Disposable { + public var cancellable: some Cancellable { + DisposableCancellable(self) + } +} + +private struct DisposableCancellable: Cancellable { + private let disposable: Disposable + + init(_ disposable: Disposable) { + self.disposable = disposable + } + + func cancel() { + self.disposable.dispose() + } +} diff --git a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift new file mode 100644 index 00000000..afa476c7 --- /dev/null +++ b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift @@ -0,0 +1,213 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine +import ReactiveSwift + +// From https://github.com/ReactiveCocoa/ReactiveSwift/pull/776/files#diff-d8195adf8a5f3283e072483fd9699c90 +extension SignalProducerConvertible { + public func eraseToAnyPublisher() -> AnyPublisher { + self.publisher.eraseToAnyPublisher() + } + + var publisher: Publishers.SignalProducerPublisher { + Publishers.SignalProducerPublisher(self.producer) + } +} + +extension Publishers { + public struct SignalProducerPublisher: Publisher { + public let base: SignalProducer + + public init(_ base: SignalProducer) { + self.base = base + } + + public func receive(subscriber: Subscriber) where Subscriber.Input == Output, Subscriber.Failure == Failure { + let subscription = SignalProducerSubscription(subscriber: subscriber, base: base) + subscription.bootstrap() + } + } +} + +private final class SignalProducerSubscription: Combine.Subscription { + typealias Output = Subscriber.Input + typealias Failure = Subscriber.Failure + + let subscriber: Subscriber + let base: SignalProducer + private let state: ReactiveSwift.Atomic + + init(subscriber: Subscriber, base: SignalProducer) { + self.subscriber = subscriber + self.base = base + self.state = ReactiveSwift.Atomic(State()) + } + + func bootstrap() { + subscriber.receive(subscription: self) + } + + func request(_ incoming: Subscribers.Demand) { + let response: DemandResponse = state.modify { state in + guard state.hasCancelled == false else { + return .noAction + } + + guard state.hasStarted else { + state.hasStarted = true + state.requested = incoming + return .startUpstream + } + + state.requested = state.requested + incoming + let unsatified = state.requested - state.satisfied + + if let max = unsatified.max { + let dequeueCount = Swift.min(state.buffer.count, max) + state.satisfied += dequeueCount + + defer { state.buffer.removeFirst(dequeueCount) } + return .satisfyDemand(Array(state.buffer.prefix(dequeueCount))) + } else { + defer { state.buffer = [] } + return .satisfyDemand(state.buffer) + } + } + + switch response { + case let .satisfyDemand(output): + var demand: Subscribers.Demand = .none + + for output in output { + demand += subscriber.receive(output) + } + + if demand != .none { + request(demand) + } + + case .startUpstream: + let disposable = base.start { [weak self] event in + guard let self = self else { return } + + switch event { + case let .value(output): + let (shouldSendImmediately, isDemandUnlimited): (Bool, Bool) = self.state.modify { state in + guard state.hasCancelled == false else { return (false, false) } + + let unsatified = state.requested - state.satisfied + + if let count = unsatified.max, count >= 1 { + assert(state.buffer.count == 0) + state.satisfied += 1 + return (true, false) + } else if unsatified == .unlimited { + assert(state.buffer.isEmpty) + return (true, true) + } else { + assert(state.requested == state.satisfied) + state.buffer.append(output) + return (false, false) + } + } + + if shouldSendImmediately { + let demand = self.subscriber.receive(output) + + if isDemandUnlimited == false && demand != .none { + self.request(demand) + } + } + + case .completed, .interrupted: + self.cancel() + self.subscriber.receive(completion: .finished) + + case let .failed(error): + self.cancel() + self.subscriber.receive(completion: .failure(error)) + } + } + + let shouldDispose: Bool = state.modify { state in + guard state.hasCancelled == false else { return true } + state.producerSubscription = disposable + return false + } + + if shouldDispose { + disposable.dispose() + } + + case .noAction: + break + } + } + + func cancel() { + let disposable = state.modify { $0.cancel() } + disposable?.dispose() + } + + struct State { + var requested: Subscribers.Demand = .none + var satisfied: Subscribers.Demand = .none + + var buffer: [Output] = [] + + var producerSubscription: Disposable? + var hasStarted = false + var hasCancelled = false + + init() { + producerSubscription = nil + hasStarted = false + hasCancelled = false + } + + mutating func cancel() -> Disposable? { + hasCancelled = true + defer { producerSubscription = nil } + return producerSubscription + } + } + + enum DemandResponse { + case startUpstream + case satisfyDemand([Output]) + case noAction + } +} + + +extension Cancellable { + var disposable: some Disposable { + CancellableDisposable(self) + } +} + +private final class CancellableDisposable: Disposable { + private let cancellable: Cancellable + private(set) var isDisposed: Bool = false + + init(_ cancellable: Cancellable) { + self.cancellable = cancellable + } + + func dispose() { + self.cancellable.cancel() + self.isDisposed = true + } +} diff --git a/FueledUtils/ReactiveCommon/ActionErrorProtocol.swift b/FueledUtils/ReactiveCommon/ActionErrorProtocol.swift new file mode 100644 index 00000000..9c4c3768 --- /dev/null +++ b/FueledUtils/ReactiveCommon/ActionErrorProtocol.swift @@ -0,0 +1,39 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. + +/// +/// A protocol for `ActionError` for use in type constraints +/// +public protocol ActionErrorProtocol: Swift.Error { + /// + /// Type of the error associated with the action error. + /// + associatedtype InnerError: Swift.Error + + /// + /// Whether the receiver is currently in the disabled state or not. + /// + var isDisabled: Bool { get } + + /// + /// The error the action protocol currently have. If `nil`, the action error is considered `disabled`. + /// + var innerError: InnerError? { get } +} + +extension ActionErrorProtocol { + public var isDisabled: Bool { + self.innerError == nil + } +} diff --git a/FueledUtils/ReactiveSwift/ActionError+ActionErrorProtocol.swift b/FueledUtils/ReactiveSwift/ActionError+ActionErrorProtocol.swift new file mode 100644 index 00000000..27e7ee47 --- /dev/null +++ b/FueledUtils/ReactiveSwift/ActionError+ActionErrorProtocol.swift @@ -0,0 +1,27 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 ReactiveSwift + +extension ReactiveSwift.ActionError: ActionErrorProtocol { + /// + /// The error the action protocol currently have. If `nil`, the action error is considered `disabled`. + /// + public var innerError: Error? { + if case .producerFailed(let error) = self { + return error + } + return nil + } +} diff --git a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift index 254f15a9..be08060c 100644 --- a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift +++ b/FueledUtils/ReactiveSwift/InputCoalescingAction.swift @@ -14,13 +14,6 @@ import ReactiveSwift -/// -/// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, -/// and will be completed with the same output when the initial executing action completes. -/// Disposing any of the `SignalProducer` returned by 'apply()` will cancel the action. -/// -public typealias CoalescingAction = InputCoalescingAction - /// /// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, /// and will be completed with the same output when the initial executing action completes. @@ -30,8 +23,8 @@ public typealias CoalescingAction = InputCoalescingA /// calls to `apply()` when the action is executing, the inputs will be ignored until /// the action terminates. /// -public class InputCoalescingAction: ActionProtocol { - private let action: Action +public class ReactiveCoalescingAction: ReactiveActionProtocol { + private let action: ReactiveSwift.Action private var observer: Signal.Observer? private class DisposableContainer { @@ -117,7 +110,7 @@ public class InputCoalescingAction: ActionPro /// executed by the `Action`. /// public init(execute: @escaping (Input) -> SignalProducer) { - self.action = Action(execute: execute) + self.action = ReactiveSwift.Action(execute: execute) } /// diff --git a/FueledUtils/ReactiveSwift/LoadingState.swift b/FueledUtils/ReactiveSwift/LoadingState.swift index 67ee5c3e..564868fc 100644 --- a/FueledUtils/ReactiveSwift/LoadingState.swift +++ b/FueledUtils/ReactiveSwift/LoadingState.swift @@ -55,7 +55,7 @@ public enum LoadingState { } } -extension ActionProtocol { +extension ReactiveActionProtocol { /// /// Returns a `SignalProducer` whose events corresponds to the current loading state of the action. /// Please refer to `LoadingState` for more info. diff --git a/FueledUtils/ReactiveSwift/ActionProtocol.swift b/FueledUtils/ReactiveSwift/ReactiveActionProtocol.swift similarity index 75% rename from FueledUtils/ReactiveSwift/ActionProtocol.swift rename to FueledUtils/ReactiveSwift/ReactiveActionProtocol.swift index aa67d279..aa53008a 100644 --- a/FueledUtils/ReactiveSwift/ActionProtocol.swift +++ b/FueledUtils/ReactiveSwift/ReactiveActionProtocol.swift @@ -15,49 +15,10 @@ import Foundation import ReactiveSwift -/// -/// An optional protocol for `ActionError` for use in type constraints -/// -public protocol ActionErrorProtocol: Swift.Error { - /// - /// Type of the error associated with the action error. - /// - associatedtype SubError: Swift.Error - - /// - /// Whether the receiver is currently in the disabled state or not. - /// - var isDisabled: Bool { get } - - /// - /// The error the action protocol currently have. If `nil`, the action error is considered `disabled`. - /// - var error: SubError? { get } -} - -extension ActionError: ActionErrorProtocol { - /// - /// Whether the receiver is currently in the disabled state or not. - /// - public var isDisabled: Bool { - return self.error == nil - } - - /// - /// The error the action protocol currently have. If `nil`, the action error is considered `disabled`. - /// - public var error: Error? { - if case .producerFailed(let error) = self { - return error - } - return nil - } -} - /// /// A protocol for `Action`s for generic constraints and code reuse. /// -public protocol ActionProtocol { +public protocol ReactiveActionProtocol { /// /// The type of the values output from the action. /// @@ -124,10 +85,10 @@ public protocol ActionProtocol { func apply(_ input: Input) -> SignalProducer } -extension Action: ActionProtocol { +extension ReactiveSwift.Action: ReactiveActionProtocol { } -extension ActionProtocol where Input == Void { +extension ReactiveActionProtocol where Input == Void { /// /// Create a `SignalProducer` that would attempt to create and start a unit of work of /// the `Action`. The `SignalProducer` would forward only events generated by the unit diff --git a/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift index 9ae2a190..99645a42 100644 --- a/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveSwiftExtensions.swift @@ -529,7 +529,7 @@ infix operator <~> : AssignmentPrecedence return disposable } -extension ActionProtocol { +extension ReactiveActionProtocol { /// /// A signal of all values or errors generated from all units of work of the `Action`. /// diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index f09486a3..13612bf1 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -235,15 +235,19 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-Combine-CombineOperators-Core-SwiftUI-UIKit/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/FueledUtils-51945db7/FueledUtils.framework", "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveCocoa-iOS13.0/ReactiveCocoa.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveSwift-iOS13.0/ReactiveSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveSwift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -257,11 +261,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-Core-ReactiveSwift-ReactiveSwiftUIKit-UIKit/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/FueledUtils-c6ea1015/FueledUtils.framework", "${BUILT_PRODUCTS_DIR}/Nimble-iOS8.0/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick-iOS8.0/Quick.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveSwift/ReactiveSwift.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveCocoa-iOS8.0/ReactiveCocoa.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveSwift-iOS8.0/ReactiveSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( diff --git a/Tests/Podfile b/Tests/Podfile index 5efeb07d..cd9a5d8f 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -14,8 +14,6 @@ abstract_target 'Common' do target 'FueledUtilsTests-SwiftUI' do platform :ios, '13.0' - pod 'FueledUtils/CombineOperators', path: '../' - pod 'FueledUtils/SwiftUI', path: '../' - pod 'FueledUtils/UIKit', path: '../' + pod 'FueledUtils/ReactiveCombineBridge', path: '../' end end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 30f5680c..5b2c15ab 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,18 +1,22 @@ PODS: - - FueledUtils/Combine (3.0) - - FueledUtils/CombineOperators (3.0): + - FueledUtils/Combine (3.0-alpha1): + - FueledUtils/ReactiveCommon + - FueledUtils/CombineOperators (3.0-alpha1): - FueledUtils/Combine - - FueledUtils/Core (3.0) - - FueledUtils/ReactiveSwift (3.0): + - FueledUtils/Core (3.0-alpha1) + - FueledUtils/ReactiveCombineBridge (3.0-alpha1): + - FueledUtils/CombineOperators + - FueledUtils/ReactiveSwift + - FueledUtils/ReactiveCommon (3.0-alpha1): + - FueledUtils/Core + - FueledUtils/ReactiveSwift (3.0-alpha1): + - FueledUtils/ReactiveCommon - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) - - FueledUtils/ReactiveSwiftUIKit (3.0): + - FueledUtils/ReactiveSwiftUIKit (3.0-alpha1): - FueledUtils/ReactiveSwift - FueledUtils/UIKit - - FueledUtils/SwiftUI (3.0): - - FueledUtils/Combine - - FueledUtils/Core - - FueledUtils/UIKit (3.0): + - FueledUtils/UIKit (3.0-alpha1): - FueledUtils/Core - Nimble (8.0.5) - Quick (2.2.0) @@ -21,10 +25,8 @@ PODS: - ReactiveSwift (6.2.1) DEPENDENCIES: - - FueledUtils/CombineOperators (from `../`) + - FueledUtils/ReactiveCombineBridge (from `../`) - FueledUtils/ReactiveSwiftUIKit (from `../`) - - FueledUtils/SwiftUI (from `../`) - - FueledUtils/UIKit (from `../`) - Nimble (~> 8.0) - Quick (~> 2.0) @@ -40,12 +42,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: cb348b77e24486d5a8c2dc3d7b65bdb2cdff277d + FueledUtils: 440829476734457153856d2e60eb100fff7c001b Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 -PODFILE CHECKSUM: 8c906577889305cc0ee68800bda92e89168239fc +PODFILE CHECKSUM: 9eb05feb683f00d2d8a16e1597307404317ad72e COCOAPODS: 1.9.1 diff --git a/Tests/ReactiveSwift/CoalescingActionSpec.swift b/Tests/ReactiveSwift/CoalescingActionSpec.swift index 685ac881..3bd7dd5d 100644 --- a/Tests/ReactiveSwift/CoalescingActionSpec.swift +++ b/Tests/ReactiveSwift/CoalescingActionSpec.swift @@ -26,7 +26,7 @@ class CoalescingActionSpec: QuickSpec { var startCounter = 0 var disposeCounter = 0 var interruptedCounter = 0 - let coalescingAction = CoalescingAction { + let coalescingAction = ReactiveCoalescingAction { SignalProducer(value: 2.0) .delay(1.0, on: QueueScheduler.main) .on( diff --git a/Tests/SwiftUI/EmptySpec.swift b/Tests/SwiftUI/EmptySpec.swift index 3b01d398..cdc27e45 100644 --- a/Tests/SwiftUI/EmptySpec.swift +++ b/Tests/SwiftUI/EmptySpec.swift @@ -14,6 +14,7 @@ import Quick import Nimble +import FueledUtils class CoalescingActionSpec: QuickSpec { override func spec() { From e7fde5545e0d712eeb6345b1746f59a3e2427204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 12 May 2020 14:42:48 -0400 Subject: [PATCH 23/89] refactor(tests): refactor tests target to only include one --- Tests/FueledUtils.xcodeproj/project.pbxproj | 232 +++--------------- .../FueledUtilsTests-ReactiveSwift.xcscheme | 4 +- .../FueledUtilsTests-SwiftUI.xcscheme | 4 +- Tests/Podfile | 9 +- Tests/Podfile.lock | 9 +- Tests/SwiftUI/EmptySpec.swift | 27 -- .../CoalescingActionSpec.swift | 0 .../ReactiveSwiftExtensionsSpec.swift | 0 8 files changed, 37 insertions(+), 248 deletions(-) delete mode 100644 Tests/SwiftUI/EmptySpec.swift rename Tests/{ReactiveSwift => Tests}/CoalescingActionSpec.swift (100%) rename Tests/{ReactiveSwift => Tests}/ReactiveSwiftExtensionsSpec.swift (100%) diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 13612bf1..a81e777f 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -7,28 +7,26 @@ objects = { /* Begin PBXBuildFile section */ - CBAC4FA461D4F6B2536EF7AB /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */; }; + EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; - F463C74724183914000A0B29 /* EmptySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C74624183914000A0B29 /* EmptySpec.swift */; }; - FDC158127A32ACE063DE046F /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 00B6C3B54CADD6729AF81F36 /* Pods-Common-FueledUtilsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.release.xcconfig"; sourceTree = ""; }; 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-ReactiveSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; + D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FueledUtilsTests-SwiftUI.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - F463C74624183914000A0B29 /* EmptySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySpec.swift; sourceTree = ""; }; + F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; /* End PBXFileReference section */ @@ -37,15 +35,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FDC158127A32ACE063DE046F /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F463C74124183913000A0B29 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CBAC4FA461D4F6B2536EF7AB /* Pods_Common_FueledUtilsTests_SwiftUI.framework in Frameworks */, + EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -59,6 +49,8 @@ ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */, 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */, 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, + D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */, + 00B6C3B54CADD6729AF81F36 /* Pods-Common-FueledUtilsTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -67,8 +59,7 @@ isa = PBXGroup; children = ( 607FACF51AFB993E008FA782 /* Podspec Metadata */, - F463C739241835DD000A0B29 /* ReactiveSwift */, - F463C74524183914000A0B29 /* SwiftUI */, + F463C739241835DD000A0B29 /* Tests */, F463C73E241835EA000A0B29 /* Supporting Files */, 607FACD11AFB9204008FA782 /* Products */, 29F7E676D0DA9F23C4893ECA /* Pods */, @@ -79,8 +70,7 @@ 607FACD11AFB9204008FA782 /* Products */ = { isa = PBXGroup; children = ( - 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */, - F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */, + 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */, ); name = Products; sourceTree = ""; @@ -98,19 +88,19 @@ 8CF3F11B25F743ED68711CBC /* Frameworks */ = { isa = PBXGroup; children = ( - 410CDB623197A6F0D05837BE /* Pods_Common_FueledUtilsTests_ReactiveSwift.framework */, 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, + F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */, ); name = Frameworks; sourceTree = ""; }; - F463C739241835DD000A0B29 /* ReactiveSwift */ = { + F463C739241835DD000A0B29 /* Tests */ = { isa = PBXGroup; children = ( F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, ); - path = ReactiveSwift; + path = Tests; sourceTree = ""; }; F463C73E241835EA000A0B29 /* Supporting Files */ = { @@ -121,20 +111,12 @@ path = "Supporting Files"; sourceTree = ""; }; - F463C74524183914000A0B29 /* SwiftUI */ = { - isa = PBXGroup; - children = ( - F463C74624183914000A0B29 /* EmptySpec.swift */, - ); - path = SwiftUI; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 607FACE41AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift */ = { + 607FACE41AFB9204008FA782 /* FueledUtilsTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */; buildPhases = ( 7A704080E54E9F7FB6D2806D /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, @@ -146,28 +128,9 @@ ); dependencies = ( ); - name = "FueledUtilsTests-ReactiveSwift"; + name = FueledUtilsTests; productName = Tests; - productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - F463C74324183913000A0B29 /* FueledUtilsTests-SwiftUI */ = { - isa = PBXNativeTarget; - buildConfigurationList = F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */; - buildPhases = ( - C83C49377CB4DCCA96B27625 /* [CP] Check Pods Manifest.lock */, - F463C74024183913000A0B29 /* Sources */, - F463C74124183913000A0B29 /* Frameworks */, - F463C74224183913000A0B29 /* Resources */, - 3EB0A0B44D7FAD0AAD39085B /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "FueledUtilsTests-SwiftUI"; - productName = "FueledUtilsTests-Combine"; - productReference = F463C74424183913000A0B29 /* FueledUtilsTests-SwiftUI.xctest */; + productReference = 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -184,11 +147,6 @@ CreatedOnToolsVersion = 6.3.1; LastSwiftMigration = 0900; }; - F463C74324183913000A0B29 = { - CreatedOnToolsVersion = 11.4; - DevelopmentTeam = 2GVJ6JS822; - ProvisioningStyle = Automatic; - }; }; }; buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FueledUtils" */; @@ -204,8 +162,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 607FACE41AFB9204008FA782 /* FueledUtilsTests-ReactiveSwift */, - F463C74324183913000A0B29 /* FueledUtilsTests-SwiftUI */, + 607FACE41AFB9204008FA782 /* FueledUtilsTests */, ); }; /* End PBXProject section */ @@ -218,54 +175,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F463C74224183913000A0B29 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3EB0A0B44D7FAD0AAD39085B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-51945db7/FueledUtils.framework", - "${BUILT_PRODUCTS_DIR}/Nimble-iOS13.0/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Quick-iOS13.0/Quick.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveCocoa-iOS13.0/ReactiveCocoa.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveSwift-iOS13.0/ReactiveSwift.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FueledUtils.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveCocoa.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveSwift.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 41CB9636F71B84882C2E6A45 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FueledUtils-c6ea1015/FueledUtils.framework", - "${BUILT_PRODUCTS_DIR}/Nimble-iOS8.0/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Quick-iOS8.0/Quick.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveCocoa-iOS8.0/ReactiveCocoa.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveSwift-iOS8.0/ReactiveSwift.framework", + "${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FueledUtils/FueledUtils.framework", + "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveCocoa/ReactiveCocoa.framework", + "${BUILT_PRODUCTS_DIR}/ReactiveSwift/ReactiveSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -277,7 +201,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 7A704080E54E9F7FB6D2806D /* [CP] Check Pods Manifest.lock */ = { @@ -295,29 +219,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-ReactiveSwift-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C83C49377CB4DCCA96B27625 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-SwiftUI-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Common-FueledUtilsTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -336,14 +238,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F463C74024183913000A0B29 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F463C74724183914000A0B29 /* EmptySpec.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ @@ -395,7 +289,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -444,7 +337,6 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -454,7 +346,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */; + baseConfigurationReference = D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -475,7 +367,7 @@ }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */; + baseConfigurationReference = 00B6C3B54CADD6729AF81F36 /* Pods-Common-FueledUtilsTests.release.xcconfig */; buildSettings = { FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -490,63 +382,6 @@ }; name = Release; }; - F463C74A24183914000A0B29 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 2GVJ6JS822; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtilsTests-Combine"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 13.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; - }; - name = Debug; - }; - F463C74B24183914000A0B29 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 2GVJ6JS822; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "Supporting Files/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.fueled.FueledUtilsTests-Combine"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 13.0; - WATCHOS_DEPLOYMENT_TARGET = 6.0; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -559,7 +394,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-ReactiveSwift" */ = { + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FueledUtilsTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 607FACF31AFB9204008FA782 /* Debug */, @@ -568,15 +403,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F463C74924183914000A0B29 /* Build configuration list for PBXNativeTarget "FueledUtilsTests-SwiftUI" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F463C74A24183914000A0B29 /* Debug */, - F463C74B24183914000A0B29 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 607FACC81AFB9204008FA782 /* Project object */; diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme index 4b05ee9d..4fe5714e 100644 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme @@ -17,8 +17,8 @@ diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme index 909350b9..db2d8d6c 100644 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme @@ -17,8 +17,8 @@ 2.0' pod 'Nimble', '~> 8.0' - target 'FueledUtilsTests-ReactiveSwift' do - platform :ios, '8.0' - - pod 'FueledUtils/ReactiveSwiftUIKit', path: '../' - end - - target 'FueledUtilsTests-SwiftUI' do + target 'FueledUtilsTests' do platform :ios, '13.0' pod 'FueledUtils/ReactiveCombineBridge', path: '../' + pod 'FueledUtils/CombineOperators', path: '../' end end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 5b2c15ab..06e5573e 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -13,11 +13,6 @@ PODS: - FueledUtils/ReactiveCommon - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) - - FueledUtils/ReactiveSwiftUIKit (3.0-alpha1): - - FueledUtils/ReactiveSwift - - FueledUtils/UIKit - - FueledUtils/UIKit (3.0-alpha1): - - FueledUtils/Core - Nimble (8.0.5) - Quick (2.2.0) - ReactiveCocoa (10.2.0): @@ -25,8 +20,8 @@ PODS: - ReactiveSwift (6.2.1) DEPENDENCIES: + - FueledUtils/CombineOperators (from `../`) - FueledUtils/ReactiveCombineBridge (from `../`) - - FueledUtils/ReactiveSwiftUIKit (from `../`) - Nimble (~> 8.0) - Quick (~> 2.0) @@ -48,6 +43,6 @@ SPEC CHECKSUMS: ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 -PODFILE CHECKSUM: 9eb05feb683f00d2d8a16e1597307404317ad72e +PODFILE CHECKSUM: d628ef51c9ea3b161c7d0f9f054d9bf7fa706a8b COCOAPODS: 1.9.1 diff --git a/Tests/SwiftUI/EmptySpec.swift b/Tests/SwiftUI/EmptySpec.swift deleted file mode 100644 index cdc27e45..00000000 --- a/Tests/SwiftUI/EmptySpec.swift +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2020 Fueled Digital Media, LLC -// -// 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 Quick -import Nimble -import FueledUtils - -class CoalescingActionSpec: QuickSpec { - override func spec() { - describe("Nothing") { - it("should pass") { - expect(true) == true - } - } - } -} diff --git a/Tests/ReactiveSwift/CoalescingActionSpec.swift b/Tests/Tests/CoalescingActionSpec.swift similarity index 100% rename from Tests/ReactiveSwift/CoalescingActionSpec.swift rename to Tests/Tests/CoalescingActionSpec.swift diff --git a/Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift b/Tests/Tests/ReactiveSwiftExtensionsSpec.swift similarity index 100% rename from Tests/ReactiveSwift/ReactiveSwiftExtensionsSpec.swift rename to Tests/Tests/ReactiveSwiftExtensionsSpec.swift From 22e80a3b61f369c438d1b9b265736bb4735910bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 12 May 2020 15:22:51 -0400 Subject: [PATCH 24/89] fix(visibility): fix visibility of some internal methods to public --- FueledUtils/UIKit/UIExtensions.swift | 10 +++++----- Tests/Podfile | 3 +++ Tests/Podfile.lock | 13 ++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/FueledUtils/UIKit/UIExtensions.swift b/FueledUtils/UIKit/UIExtensions.swift index 32625e2d..19a91a52 100644 --- a/FueledUtils/UIKit/UIExtensions.swift +++ b/FueledUtils/UIKit/UIExtensions.swift @@ -33,7 +33,7 @@ extension CGSize { /// In all other cases, the receiver is returned and `size` is ignored. /// - Returns: The scaled size as specified by the parameters. /// - func scaled(to size: CGSize, contentMode: UIView.ContentMode = .scaleToFill) -> CGSize { + public func scaled(to size: CGSize, contentMode: UIView.ContentMode = .scaleToFill) -> CGSize { switch contentMode { case .redraw, .scaleToFill: @@ -299,7 +299,7 @@ extension UIView { /// /// - Note: It is safe to call this method multiple times without calling `removeSketchShadow` between each calls. /// - func applySketchShadow( + public func applySketchShadow( color: UIColor = .black, alpha: Float = 0.5, xAxis: CGFloat = 0.0, @@ -327,7 +327,7 @@ extension UIView { /// Remove a shadow as set by `applySketchShadow` /// This method will reset any shadows sets on the backing layer, _even it wasn't applied by `applySketchShadow` /// - func removeSketchShadow() { + public func removeSketchShadow() { self.layer.shadowColor = nil self.layer.shadowOpacity = 0.0 self.layer.shadowOffset = .zero @@ -346,7 +346,7 @@ extension UIStackView { /// - removeFromHierachy: If `true`, each views is also removed from the receiver using `removeFromSuperview()`. /// If `false`, `removeFromSuperview()` is not called. /// - func removeArrangedSubviews(removeFromHierachy: Bool) { + public func removeArrangedSubviews(removeFromHierachy: Bool) { let arrangedSubviews = self.arrangedSubviews arrangedSubviews.forEach { self.removeArrangedSubview($0, removeFromHierachy: removeFromHierachy) } } @@ -360,7 +360,7 @@ extension UIStackView { /// - removeFromHierachy: If `true`, the view is also removed from the receiver using `removeFromSuperview()`. /// If `false`, `removeFromSuperview()` is not called. /// - func removeArrangedSubview(_ view: UIView, removeFromHierachy: Bool) { + public func removeArrangedSubview(_ view: UIView, removeFromHierachy: Bool) { if removeFromHierachy { view.removeFromSuperview() } else { diff --git a/Tests/Podfile b/Tests/Podfile index 5d72607f..abd60906 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -9,6 +9,9 @@ abstract_target 'Common' do platform :ios, '13.0' pod 'FueledUtils/ReactiveCombineBridge', path: '../' + pod 'FueledUtils/ReactiveSwift', path: '../' + pod 'FueledUtils/ReactiveSwiftUIKit', path: '../' + pod 'FueledUtils/SwiftUI', path: '../' pod 'FueledUtils/CombineOperators', path: '../' end end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 06e5573e..b4a499e5 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -13,6 +13,14 @@ PODS: - FueledUtils/ReactiveCommon - ReactiveCocoa (~> 10.0) - ReactiveSwift (~> 6.0) + - FueledUtils/ReactiveSwiftUIKit (3.0-alpha1): + - FueledUtils/ReactiveSwift + - FueledUtils/UIKit + - FueledUtils/SwiftUI (3.0-alpha1): + - FueledUtils/Combine + - FueledUtils/Core + - FueledUtils/UIKit (3.0-alpha1): + - FueledUtils/Core - Nimble (8.0.5) - Quick (2.2.0) - ReactiveCocoa (10.2.0): @@ -22,6 +30,9 @@ PODS: DEPENDENCIES: - FueledUtils/CombineOperators (from `../`) - FueledUtils/ReactiveCombineBridge (from `../`) + - FueledUtils/ReactiveSwift (from `../`) + - FueledUtils/ReactiveSwiftUIKit (from `../`) + - FueledUtils/SwiftUI (from `../`) - Nimble (~> 8.0) - Quick (~> 2.0) @@ -43,6 +54,6 @@ SPEC CHECKSUMS: ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 -PODFILE CHECKSUM: d628ef51c9ea3b161c7d0f9f054d9bf7fa706a8b +PODFILE CHECKSUM: aeaba40223e95702eeaff4c66b27d8c72fcee5f5 COCOAPODS: 1.9.1 From 3a5734cb868927364a3cbd5d2ca7d6884acbaca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 13 May 2020 09:25:23 -0400 Subject: [PATCH 25/89] fix(combine/operators): add support for importing FueledUtils & ReactiveSwift when CombineOperators are used --- FueledUtils/CombineOperators/Combine+Operators.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FueledUtils/CombineOperators/Combine+Operators.swift b/FueledUtils/CombineOperators/Combine+Operators.swift index 09e7b561..8815e7e1 100644 --- a/FueledUtils/CombineOperators/Combine+Operators.swift +++ b/FueledUtils/CombineOperators/Combine+Operators.swift @@ -16,6 +16,7 @@ import Combine // swiftlint:disable generic_type_name +#if !canImport(ReactiveSwift) precedencegroup BindingPrecedence { associativity: right @@ -23,6 +24,9 @@ precedencegroup BindingPrecedence { } infix operator <~: BindingPrecedence +#else +import ReactiveSwift +#endif precedencegroup AccessPrecedence { associativity: right From 07df8f92174f1974d3aa0ebde93a27e8fdd327df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 30 Apr 2020 09:20:12 -0400 Subject: [PATCH 26/89] feat(ordered-set): add Codable conformance to OrderedSet --- FueledUtils/Core/OrderedSet.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FueledUtils/Core/OrderedSet.swift b/FueledUtils/Core/OrderedSet.swift index ddc12f49..befb32f9 100644 --- a/FueledUtils/Core/OrderedSet.swift +++ b/FueledUtils/Core/OrderedSet.swift @@ -145,3 +145,6 @@ extension OrderedSet: RandomAccessCollection { extension OrderedSet: Hashable where Element: Hashable { } + +extension OrderedSet: Codable where Element: Codable { +} From 7b75311212592c9828fc78a68ef8cc0c2035fe05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 8 May 2020 11:33:30 -0400 Subject: [PATCH 27/89] fix(ordered-set): fix replaceSubrange() method not working as expected --- FueledUtils/Core/OrderedSet.swift | 35 ++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/FueledUtils/Core/OrderedSet.swift b/FueledUtils/Core/OrderedSet.swift index befb32f9..3cb3bdda 100644 --- a/FueledUtils/Core/OrderedSet.swift +++ b/FueledUtils/Core/OrderedSet.swift @@ -31,13 +31,13 @@ public struct OrderedSet: Equatable, RangeReplaceableCollection { self.set = Set() } - /// Creates an ordered set with the contents of `array`. + /// Creates an ordered set with the contents of `sequence`. /// - /// If an element occurs more than once in `element`, only the first one + /// If an element occurs more than once in `sequence`, only the first one /// will be included. - public init(_ array: [Element]) { + public init(_ sequence: Sequence) where Sequence.Element == Self.Element { self.init() - for element in array { + for element in sequence { self.append(element) } } @@ -78,9 +78,19 @@ public struct OrderedSet: Equatable, RangeReplaceableCollection { return inserted } - public mutating func replaceSubrange(_ subrange: R, with newElements: C) where C: Collection, R: RangeExpression, Element == C.Element, Index == R.Bound { - self.array.replaceSubrange(subrange, with: newElements) - self.set = Set(self.array) + public mutating func replaceSubrange< + Collection: Swift.Collection, + Range: RangeExpression + >( + _ subrange: Range, + with newElements: Collection + ) where + Collection.Element == Element, + Range.Bound == Index + { + let newElementsOrderedSet = OrderedSet(newElements) + self.array.replaceSubrange(subrange, with: newElementsOrderedSet) + self.set.formUnion(newElements) } /// Remove and return the element at the beginning of the ordered set. @@ -146,5 +156,14 @@ extension OrderedSet: RandomAccessCollection { extension OrderedSet: Hashable where Element: Hashable { } -extension OrderedSet: Codable where Element: Codable { +extension OrderedSet: Decodable where Element: Decodable { + public init(from decoder: Decoder) throws { + try self.init([Element](from: decoder)) + } +} + +extension OrderedSet: Encodable where Element: Encodable { + public func encode(to encoder: Encoder) throws { + try self.array.encode(to: encoder) + } } From ec8e62b8da4b8c59df7493668e9427cdc81ec321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 13 May 2020 13:09:34 -0400 Subject: [PATCH 28/89] fix(blur): fix segfault when compiling an app using `backgroundBlur` with optimizations enabled --- FueledUtils/SwiftUI/BackgroundBlur.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index 6c9a551c..0260b960 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -32,6 +32,7 @@ extension View { color BlurView(style: style) self + .eraseToAnyView() } } } From f70301b6e04bce57940d8f46c770cbeab916c81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 13 May 2020 13:11:26 -0400 Subject: [PATCH 29/89] chore(blur): move BlurView to its own file --- FueledUtils/SwiftUI/BackgroundBlur.swift | 12 ----------- FueledUtils/SwiftUI/BlurView.swift | 27 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 FueledUtils/SwiftUI/BlurView.swift diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index 0260b960..39867330 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -14,18 +14,6 @@ import SwiftUI -public struct BlurView: UIViewRepresentable { - public let style: UIBlurEffect.Style - - public func makeUIView(context: Context) -> UIVisualEffectView { - UIVisualEffectView(effect: UIBlurEffect(style: self.style)) - } - - public func updateUIView(_ visualEffectView: UIVisualEffectView, context: Context) { - visualEffectView.effect = UIBlurEffect(style: self.style) - } -} - extension View { public func backgroundBlur(style: UIBlurEffect.Style, color: Color? = nil) -> some View { ZStack { diff --git a/FueledUtils/SwiftUI/BlurView.swift b/FueledUtils/SwiftUI/BlurView.swift new file mode 100644 index 00000000..d2c3c3fc --- /dev/null +++ b/FueledUtils/SwiftUI/BlurView.swift @@ -0,0 +1,27 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 SwiftUI + +public struct BlurView: UIViewRepresentable { + public let style: UIBlurEffect.Style + + public func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: self.style)) + } + + public func updateUIView(_ visualEffectView: UIVisualEffectView, context: Context) { + visualEffectView.effect = UIBlurEffect(style: self.style) + } +} From f9f1bf326fb3afe152f417ae6bf9c3ba0045754c Mon Sep 17 00:00:00 2001 From: Benoit Layer <1849419+notbenoit@users.noreply.github.com> Date: Mon, 8 Jul 2019 14:14:23 +0200 Subject: [PATCH 30/89] feat(regex): match and return regex captured groups --- FueledUtils/Core/Regex.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/FueledUtils/Core/Regex.swift b/FueledUtils/Core/Regex.swift index ec8c919c..6e1952bb 100644 --- a/FueledUtils/Core/Regex.swift +++ b/FueledUtils/Core/Regex.swift @@ -59,6 +59,25 @@ public struct Regex { public func match(_ string: String, options: NSRegularExpression.MatchingOptions = []) -> Bool { return implementation.numberOfMatches(in: string, options: options, range: string.nsRange) != 0 } + + /// Match all the captured groups if any. + /// + /// - Parameters: + /// - pattern: The string to match the regex against. + /// - options: The options to use when matching the regular expression + /// against the given string. + /// - Returns: The captured groups. + /// + /// - Note: By default, NSRegularExpression exposes the matching text (not the + /// group) as the first (index 0) element of the NSTextCheckingResult. This + /// is ignored in the returned value, as it is not a captured group. + public func groups(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [[String]] { + let matches = implementation.matches(in: string, options: options, range: string.nsRange) + return matches + .map { match in (1.. Date: Mon, 31 Aug 2020 12:22:46 -0400 Subject: [PATCH 31/89] chore(swiftui): add DynamicViewContent conformance for ForEachWithIndex when Content is View --- FueledUtils/SwiftUI/ForEachWithIndex.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FueledUtils/SwiftUI/ForEachWithIndex.swift b/FueledUtils/SwiftUI/ForEachWithIndex.swift index 95b6f732..0e36901b 100644 --- a/FueledUtils/SwiftUI/ForEachWithIndex.swift +++ b/FueledUtils/SwiftUI/ForEachWithIndex.swift @@ -15,9 +15,9 @@ import SwiftUI public struct ForEachWithIndex: View { - var data: Data + public var data: Data + public var content: (_ index: Data.Index, _ element: Data.Element) -> Content var id: KeyPath - var content: (_ index: Data.Index, _ element: Data.Element) -> Content public init(_ data: Data, id: KeyPath, content: @escaping (_ index: Data.Index, _ element: Data.Element) -> Content) { self.data = data @@ -47,6 +47,9 @@ extension ForEachWithIndex where ID == Data.Element.ID, Content: View, Data.Elem } } +extension ForEachWithIndex: DynamicViewContent where Content: View { +} + private struct IndexInfo: Hashable { let index: Index let id: KeyPath From 6d51fadc9e0b7363bb4a654c58c472fb12b87b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 31 Aug 2020 12:29:29 -0400 Subject: [PATCH 32/89] fix(macos): fix macos target not compiling when including reactive swift subspec --- FueledUtils/Core/SwiftExtensions.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 FueledUtils/Core/SwiftExtensions.swift diff --git a/FueledUtils/Core/SwiftExtensions.swift b/FueledUtils/Core/SwiftExtensions.swift new file mode 100644 index 00000000..44d78bd8 --- /dev/null +++ b/FueledUtils/Core/SwiftExtensions.swift @@ -0,0 +1,24 @@ +// +// FoundationExtensions.swift +// Pods +// +// Created by Stéphane Copin on 8/31/20. +// + +extension FloatingPoint { + func rounded(decimalPlaces: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Self { + var this = self + this.round(decimalPlaces: decimalPlaces, rule: rule) + return this + } + + mutating func round(decimalPlaces: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) { + var offset = Self(1) + for _ in (0.. Date: Mon, 31 Aug 2020 12:29:57 -0400 Subject: [PATCH 33/89] feat(rounding): add round(decimalPlaces:) method on FloatingPoint --- FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift index a7de6a92..95cc0e44 100644 --- a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift @@ -15,12 +15,15 @@ import Foundation import ReactiveCocoa import ReactiveSwift +#if os(iOS) import UIKit +#endif /// /// Use with `SignalProtocol.observe(context:)` or `SignalProducerProtocol.observe(context:)` below to animate /// all changes made by observers of the signal returned from `observe(context:)`. /// +#if os(iOS) public func animatingContext( _ duration: TimeInterval, delay: TimeInterval = 0, @@ -64,6 +67,7 @@ public func transitionContext( completion: completion) } } +#endif extension Reactive where Base: NSLayoutConstraint { /// @@ -74,6 +78,7 @@ extension Reactive where Base: NSLayoutConstraint { } } +#if os(iOS) extension Reactive where Base: UIView { /// /// Update the `alpha` property of the view with an animation. @@ -121,7 +126,6 @@ extension Reactive where Base: UIViewController { } } -#if os(iOS) extension Reactive where Base: UINavigationItem { /// /// Show/hide the back button, optionally with an animation. From f5d94ad740e68814ef4525ef07b222eb5d1e8608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 31 Aug 2020 12:30:20 -0400 Subject: [PATCH 34/89] chore(podfile): update cocoapods version to 1.9.3 --- Tests/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index b4a499e5..893e8efe 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -56,4 +56,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: aeaba40223e95702eeaff4c66b27d8c72fcee5f5 -COCOAPODS: 1.9.1 +COCOAPODS: 1.9.3 From d31578208f980672396cbcab045c7eda4f535171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 31 Aug 2020 12:32:13 -0400 Subject: [PATCH 35/89] chore(macos/version): increase minimum deployment target for macOS to 10.12 --- FueledUtils.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 5eb1a523..ce4c6715 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.subspec 'Core' do |s| s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.9' + s.osx.deployment_target = '10.12' s.watchos.deployment_target = '2.0' s.tvos.deployment_target = '9.0' From 7c7638c9b0209144a5b3fc85c5a6767496e56fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 31 Aug 2020 12:50:52 -0400 Subject: [PATCH 36/89] chore(blurview): implement BlurView for macOS --- FueledUtils/SwiftUI/BackgroundBlur.swift | 24 +++++++++++++ FueledUtils/SwiftUI/BlurView.swift | 46 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index 39867330..88d0d378 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -15,6 +15,7 @@ import SwiftUI extension View { + #if os(iOS) public func backgroundBlur(style: UIBlurEffect.Style, color: Color? = nil) -> some View { ZStack { color @@ -23,4 +24,27 @@ extension View { .eraseToAnyView() } } + #else + public func backgroundBlur( + material: NSVisualEffectView.Material = .appearanceBased, + blendingMode: NSVisualEffectView.BlendingMode = .behindWindow, + state: NSVisualEffectView.State = .followsWindowActiveState, + maskImage: NSImage? = nil, + isEmphasized: Bool = false, + color: Color? = nil + ) -> some View { + ZStack { + color + BlurView( + material: material, + blendingMode: blendingMode, + state: state, + maskImage: maskImage, + isEmphasized: isEmphasized + ) + self + .eraseToAnyView() + } + } + #endif } diff --git a/FueledUtils/SwiftUI/BlurView.swift b/FueledUtils/SwiftUI/BlurView.swift index d2c3c3fc..69d0014d 100644 --- a/FueledUtils/SwiftUI/BlurView.swift +++ b/FueledUtils/SwiftUI/BlurView.swift @@ -13,10 +13,20 @@ // limitations under the License. import SwiftUI +#if os(iOS) +import UIKit +#else +import AppKit +#endif +#if os(iOS) public struct BlurView: UIViewRepresentable { public let style: UIBlurEffect.Style + public init(style: UIBlurEffect.Style) { + self.style = style + } + public func makeUIView(context: Context) -> UIVisualEffectView { UIVisualEffectView(effect: UIBlurEffect(style: self.style)) } @@ -25,3 +35,39 @@ public struct BlurView: UIViewRepresentable { visualEffectView.effect = UIBlurEffect(style: self.style) } } +#else +public struct BlurView: NSViewRepresentable { + public let material: NSVisualEffectView.Material + public let blendingMode: NSVisualEffectView.BlendingMode + public let state: NSVisualEffectView.State + public let maskImage: NSImage? + public let isEmphasized: Bool + + public init( + material: NSVisualEffectView.Material = .appearanceBased, + blendingMode: NSVisualEffectView.BlendingMode = .behindWindow, + state: NSVisualEffectView.State = .followsWindowActiveState, + maskImage: NSImage? = nil, + isEmphasized: Bool = false + ) { + self.material = material + self.blendingMode = blendingMode + self.state = state + self.maskImage = maskImage + self.isEmphasized = isEmphasized + } + + public func makeNSView(context: Context) -> NSVisualEffectView { + NSVisualEffectView() + } + + public func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) { + visualEffectView.material = self.material + visualEffectView.blendingMode = self.blendingMode + visualEffectView.state = self.state + visualEffectView.maskImage = self.maskImage + visualEffectView.isEmphasized = self.isEmphasized + } +} + +#endif From f2cf5628399dddc249995e79c9ce3ed86d9a3946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 1 Sep 2020 22:03:06 -0400 Subject: [PATCH 37/89] chore(podspec): update Podspec to not reference CombineOperators for ReactiveCombineBridge --- FueledUtils.podspec | 4 ++-- Tests/Podfile.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index ce4c6715..eeb95045 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -74,9 +74,9 @@ Pod::Spec.new do |s| s.subspec 'ReactiveCombineBridge' do |s| s.dependency 'FueledUtils/ReactiveSwift' - s.dependency 'FueledUtils/CombineOperators' + s.dependency 'FueledUtils/Combine' - s.ios.source_files = 'FueledUtils/ReactiveCombineBridge/**/*.swift' + s.source_files = 'FueledUtils/ReactiveCombineBridge/**/*.swift' end s.osx.exclude_files = ['FueledUtils/FueledUtils.h', 'FueledUtils/ButtonWithTitleAdjustment.swift', 'FueledUtils/DecoratingTextFieldDelegate.swift', 'FueledUtils/DimmingButton.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/HairlineView.swift', 'FueledUtils/KeyboardInsetHelper.swift', 'FueledUtils/LabelWithTitleAdjustment.swift', 'FueledUtils/ReactiveCocoaExtensions.swift', 'FueledUtils/ScrollViewPage.swift', 'FueledUtils/SetRootViewController.swift', 'FueledUtils/SignalingAlert.swift', 'FueledUtils/UIExtensions.swift', 'FueledUtils/GradientView.swift'] diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 893e8efe..9bfa0be7 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -5,7 +5,7 @@ PODS: - FueledUtils/Combine - FueledUtils/Core (3.0-alpha1) - FueledUtils/ReactiveCombineBridge (3.0-alpha1): - - FueledUtils/CombineOperators + - FueledUtils/Combine - FueledUtils/ReactiveSwift - FueledUtils/ReactiveCommon (3.0-alpha1): - FueledUtils/Core @@ -48,7 +48,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: 440829476734457153856d2e60eb100fff7c001b + FueledUtils: ac740e5dae55bc10029aa3cbcd8b533298622d7a Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 From 2e454b9b3d7c013c3df626681741953eb0d019ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 1 Sep 2020 22:03:22 -0400 Subject: [PATCH 38/89] fix(reactive-combine): make publisher public for SignalProducerConvertible --- FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift index afa476c7..4548944d 100644 --- a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift +++ b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift @@ -21,7 +21,7 @@ extension SignalProducerConvertible { self.publisher.eraseToAnyPublisher() } - var publisher: Publishers.SignalProducerPublisher { + public var publisher: Publishers.SignalProducerPublisher { Publishers.SignalProducerPublisher(self.producer) } } From c3ca0971d134cfdb7c3c5a53935def9d71950e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 12:36:10 -0400 Subject: [PATCH 39/89] feat(ui-view-extensions): add insets parameter to addAndFitSubview --- FueledUtils/UIKit/UIExtensions.swift | 23 +++++++++++++++------ Tests/FueledUtils.xcodeproj/project.pbxproj | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/FueledUtils/UIKit/UIExtensions.swift b/FueledUtils/UIKit/UIExtensions.swift index 19a91a52..d7f896aa 100644 --- a/FueledUtils/UIKit/UIExtensions.swift +++ b/FueledUtils/UIKit/UIExtensions.swift @@ -235,14 +235,24 @@ extension UITextField { extension UIView { /// /// Adds the given subview into the receiver, and adds constraint so that its top, bottom, left and right's edges are bounds to its superview's edges. + /// - Parameters: + /// - insets: Optionally apply an offset when adding the view. + /// - Returns: The image if it could be generated. If it couldn't, for example if the `UIView`'s width or height is 0, a crash will happen at runtime. + /// + /// - Note: The returns is an implicitely unwrapped optional for backward-compatibility purpose, and will be made an optional in a future release (as well as not crash) /// - public func addAndFitSubview(_ view: UIView) { + public func addAndFitSubview(_ view: UIView, insets: UIEdgeInsets = .zero) { view.translatesAutoresizingMaskIntoConstraints = false - view.frame = self.bounds + view.frame = self.bounds.inset(by: insets) self.addSubview(view) - let views = ["view": view] - self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", options: [], metrics: nil, views: views)) - self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|", options: [], metrics: nil, views: views)) + self.addConstraints( + [ + NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: insets.left), + NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: -insets.right), + NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: insets.top), + NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -insets.bottom), + ] + ) } /// @@ -345,8 +355,9 @@ extension UIStackView { /// - Parameters: /// - removeFromHierachy: If `true`, each views is also removed from the receiver using `removeFromSuperview()`. /// If `false`, `removeFromSuperview()` is not called. + /// This parameters defaults to `true`. /// - public func removeArrangedSubviews(removeFromHierachy: Bool) { + public func removeArrangedSubviews(removeFromHierachy: Bool = true) { let arrangedSubviews = self.arrangedSubviews arrangedSubviews.forEach { self.removeArrangedSubview($0, removeFromHierachy: removeFromHierachy) } } diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index a81e777f..47cb1822 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; - CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; }; + CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; From ab397d2a2b842c2c83067ecf48acfcfa9ad0a2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 14:24:06 -0400 Subject: [PATCH 40/89] feat(reactive/uikit/tapped): add tapped reactive extension to all UIControl --- .../ControlProtocol+Tapped.swift | 69 +++++++++++++++++++ .../ReactiveSwiftUIKit/ControlProtocol.swift | 32 +++++++++ .../ReactiveSwiftUIKit/TapAction.swift | 59 ++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift create mode 100644 FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift create mode 100644 FueledUtils/ReactiveSwiftUIKit/TapAction.swift diff --git a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift b/FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift new file mode 100644 index 00000000..36cfe991 --- /dev/null +++ b/FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift @@ -0,0 +1,69 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 ReactiveCocoa +import ReactiveSwift + +private var tapActionStorage: UInt8 = 0 +private var tapActionKey: UInt8 = 0 + +extension Reactive where Base: ControlProtocol { + /// + /// The action to be triggered when the button is tapped. + /// This mirrors the `pressed` property native in `ReactiveCocoa`, but uses a + /// protocol to represents the button rather than hardcode it to classes, + /// allowing for any `UIControl` to use this method. + /// + var tapped: TapAction? { + get { + self.tapActionStorage?.tapAction + } + nonmutating set { + self.tapActionStorage = nil + + if let newValue = newValue { + let tapActionStorage = TapActionStorage(newValue) + tapActionStorage.disposable += self.makeBindingTarget { control, isEnabled in + control.isEnabled = isEnabled + } <~ newValue.isEnabled + if self.base is ControlLoadingProtocol { + tapActionStorage.disposable += self.makeBindingTarget { control, isExecuting in + (control as! ControlLoadingProtocol).isLoading = isExecuting + } <~ newValue.isExecuting + } + self.base.removeTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) + self.base.addTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) + self.tapActionStorage = tapActionStorage + } + } + } + + private var tapActionStorage: TapActionStorage? { + get { + objc_getAssociatedObject(self.base, &tapActionKey) as? TapActionStorage + } + nonmutating set { + objc_setAssociatedObject(self.base, &tapActionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + +private final class TapActionStorage { + let tapAction: TapAction + let disposable = ScopedDisposable(CompositeDisposable()) + + init(_ tapAction: TapAction) { + self.tapAction = tapAction + } +} diff --git a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift b/FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift new file mode 100644 index 00000000..0de3ffde --- /dev/null +++ b/FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift @@ -0,0 +1,32 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 UIKit + +/// +/// A protocol that represents a control, which must be a `UIControl`. +/// +protocol ControlProtocol: UIControl { +} + +/// +/// A protocol that represents a control with a `isLoading` property. +/// +protocol ControlLoadingProtocol: ControlProtocol { + var isLoading: Bool { get set } +} + +// Make all `UIControl` a `ControlProtocol` by default. +extension UIControl: ControlProtocol { +} diff --git a/FueledUtils/ReactiveSwiftUIKit/TapAction.swift b/FueledUtils/ReactiveSwiftUIKit/TapAction.swift new file mode 100644 index 00000000..3b47b1eb --- /dev/null +++ b/FueledUtils/ReactiveSwiftUIKit/TapAction.swift @@ -0,0 +1,59 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 FueledUtils +import ReactiveSwift + +/// +/// TapAction wraps a `ReactiveActionProtocol` for use by any `ButtonProtocol` +/// This is a mirror of `CococaAction` in `ReactiveCocoa`, allowing to use a +/// `ButtonProtocol` and assigin +/// +final class TapAction: NSObject { + @objc static var selector: Selector { + #selector(userDidTapButton(_:)) + } + + let isExecuting: Property + let isEnabled: Property + + private let executeClosure: (Button) -> Void + + convenience init(_ action: Action) where Action.Input == Void { + self.init(action, input: ()) + } + + convenience init(_ action: Action, input: Action.Input) { + self.init(action) { _ in input } + } + + init(_ action: Action, inputTransform: @escaping (Button) -> Action.Input) { + self.executeClosure = { + action.apply(inputTransform($0)).start() + } + + self.isEnabled = Property( + initial: action.isEnabled.value, + then: action.isEnabled.producer.observe(on: UIScheduler()) + ) + self.isExecuting = Property( + initial: action.isExecuting.value, + then: action.isExecuting.producer.observe(on: UIScheduler()) + ) + } + + @objc private func userDidTapButton(_ button: Any) { + self.executeClosure(button as! Button) + } +} From d78dc25526e32923385ec2733833d9ff3eab4a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 14:30:26 -0400 Subject: [PATCH 41/89] feat(reactive/actions): add OverridingAction & AnyAction --- FueledUtils/ReactiveSwift/AnyAction.swift | 48 ++++++ .../ReactiveSwift/OverridingAction.swift | 147 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 FueledUtils/ReactiveSwift/AnyAction.swift create mode 100644 FueledUtils/ReactiveSwift/OverridingAction.swift diff --git a/FueledUtils/ReactiveSwift/AnyAction.swift b/FueledUtils/ReactiveSwift/AnyAction.swift new file mode 100644 index 00000000..2d43590a --- /dev/null +++ b/FueledUtils/ReactiveSwift/AnyAction.swift @@ -0,0 +1,48 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 FueledUtils +import ReactiveSwift + +/// +/// A type-erased Action that allows to store any `ReactiveActionProtocol` +/// (loosing any type information at the same time) +/// +final class AnyAction: ReactiveActionProtocol { + private let applyClosure: (Input) -> SignalProducer + private let deinitToken: Lifetime.Token + + init(_ action: Action) { + self.isEnabled = action.isEnabled + self.isExecuting = action.isExecuting + self.applyClosure = { action.apply($0 as! Action.Input).map { $0 }.mapError { $0 } } + self.events = action.events.map { $0.map { $0 }.mapError { $0 } } + self.values = action.values.map { $0 } + self.errors = action.errors.map { $0 } + (self.lifetime, self.deinitToken) = Lifetime.make() + } + + let isEnabled: Property + let isExecuting: Property + + let events: Signal.Event, Never> + let values: Signal + let errors: Signal + + let lifetime: Lifetime + + func apply(_ input: Any) -> SignalProducer { + self.applyClosure(input) + } +} diff --git a/FueledUtils/ReactiveSwift/OverridingAction.swift b/FueledUtils/ReactiveSwift/OverridingAction.swift new file mode 100644 index 00000000..7c1bdc0e --- /dev/null +++ b/FueledUtils/ReactiveSwift/OverridingAction.swift @@ -0,0 +1,147 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 FueledUtils +import ReactiveSwift + +/// +/// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, +/// and will be interrupt the previous apply(). +/// +public final class OverridingAction: ReactiveActionProtocol { + private let action: ReactiveSwift.Action + private var observer: Signal.Observer? + + private let currentDisposable = SerialDisposable(nil) + + /// + /// Whether the action is currently executing. + /// + public var isExecuting: Property { + self.action.isExecuting + } + + /// + /// Whether the action is enabled. + /// + public var isEnabled: Property { + self.action.isEnabled + } + + /// + /// A signal of all events generated from all units of work of the `Action`. + /// + /// In other words, this sends every `Event` from every unit of work that the `Action` + /// executes. + /// + public var events: Signal.Event, Never> { + self.action.events + } + + /// + /// A signal of all values generated from all units of work of the `Action`. + /// + /// In other words, this sends every value from every unit of work that the `Action` + /// executes. + /// + public var values: Signal { + self.action.values + } + + /// + /// A signal of all errors generated from all units of work of the `Action`. + /// + /// In other words, this sends every error from every unit of work that the `Action` + /// executes. + /// + public var errors: Signal { + self.action.errors + } + + /// + /// The lifetime of the `Action`. + /// + public var lifetime: Lifetime { + self.action.lifetime + } + + /// + /// Initializes an `OverridingAction`. + /// + /// When the `Action` is asked to start the execution with an input value, a unit of + /// work — represented by a `SignalProducer` — would be created by invoking + /// `execute` with the input value. + /// + /// - parameters: + /// - execute: A closure that produces a unit of work, as `SignalProducer`, to be + /// executed by the `Action`. + /// + public convenience init(execute: @escaping (Input) -> SignalProducer) { + self.init(enabledIf: Property(value: true), execute: execute) + } + + /// + /// Initializes an `OverridingAction`. + /// + /// When the `Action` is asked to start the execution with an input value, a unit of + /// work — represented by a `SignalProducer` — would be created by invoking + /// `execute` with the input value. + /// + /// - parameters: + /// - isEnabled: A property which determines the availability of the `Action`. + /// - execute: A closure that produces a unit of work, as `SignalProducer`, to be + /// executed by the `Action`. + /// + public init( + enabledIf isEnabled: Property, + execute: @escaping (Input) -> SignalProducer + ) + where Property.Value == Bool + { + self.action = ReactiveSwift.Action(enabledIf: isEnabled, execute: execute) + } + + /// + /// Create a `SignalProducer` that would attempt to create and start a unit of work of + /// the `Action`. The `SignalProducer` would forward only events generated by the unit + /// of work it created. + /// + /// - Warning: Only the first call to `apply()` when the action's `isExecuting`'s `value` is `false` will be using its parameters. + /// Subsequent calls when the action is already executing will ignore the input. + /// + /// - Parameters: + /// - input: The initial input to use for the action. + /// + /// - Returns: A producer that forwards events generated by its started unit of work. If the action was already executing, it will create a `SignalProducer` + /// that will forward the events of the initially created `SignalProducer`. + /// + public func apply(_ input: Input) -> SignalProducer { + SignalProducer { observer, lifetime in + self.currentDisposable.inner = nil + + self.observer = observer + self.currentDisposable.inner = self.action.apply(input) + .flatMapError { error in + guard case .producerFailed(let innerError) = error else { + return SignalProducer.empty + } + + return SignalProducer(error: innerError) + } + .start(observer) + + lifetime += self.currentDisposable.inner + } + } +} From 1f073d5248ef5ff62d879d14878cdc621dc002e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 14:49:51 -0400 Subject: [PATCH 42/89] feat(core/identifiable): add AnyIdentifiable --- FueledUtils/Core/AnyIdentifiable.swift | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 FueledUtils/Core/AnyIdentifiable.swift diff --git a/FueledUtils/Core/AnyIdentifiable.swift b/FueledUtils/Core/AnyIdentifiable.swift new file mode 100644 index 00000000..ec1e3650 --- /dev/null +++ b/FueledUtils/Core/AnyIdentifiable.swift @@ -0,0 +1,29 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. + +/// +/// A type-erased `Identifiable` object. +/// +@available(iOS 13, *) +struct AnyIdentifiable: Identifiable { + private let hashValueClosure: () -> AnyHashable + + init(_ identifiable: Identifiable) { + self.hashValueClosure = { AnyHashable(identifiable.id) } + } + + var id: AnyHashable { + self.hashValueClosure() + } +} From 458345865fd33505be450158dd7d321df155894a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 14:55:21 -0400 Subject: [PATCH 43/89] chore(license): add missing license headers --- FueledUtils/Core/SwiftExtensions.swift | 13 ++++++++++--- .../Combine+ReactiveSwift.swift | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/FueledUtils/Core/SwiftExtensions.swift b/FueledUtils/Core/SwiftExtensions.swift index 44d78bd8..723bfdc6 100644 --- a/FueledUtils/Core/SwiftExtensions.swift +++ b/FueledUtils/Core/SwiftExtensions.swift @@ -1,9 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// FoundationExtensions.swift -// Pods +// 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 // -// Created by Stéphane Copin on 8/31/20. +// 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. extension FloatingPoint { func rounded(decimalPlaces: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Self { diff --git a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift index 5faa596c..6023f6d4 100644 --- a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift +++ b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift @@ -1,9 +1,16 @@ +// Copyright © 2020, Fueled Digital Media, LLC // -// Combine+ReactiveSwift.swift -// FueledUtils-c6ea1015 +// 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 // -// Created by Stéphane Copin on 5/12/20. +// 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 Combine import ReactiveSwift From 4ea0e264c62d8fd275ce2e54b5023a43802ab9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 16:09:04 -0400 Subject: [PATCH 44/89] feat(ordered-set): improve OrderedSet with Algebra that would sometimes corrupt its internal state --- FueledUtils/Core/OrderedSet.swift | 73 +++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/FueledUtils/Core/OrderedSet.swift b/FueledUtils/Core/OrderedSet.swift index 3cb3bdda..d754bf0b 100644 --- a/FueledUtils/Core/OrderedSet.swift +++ b/FueledUtils/Core/OrderedSet.swift @@ -17,7 +17,7 @@ import Foundation // From https://github.com/apple/swift-package-manager/blob/4f69f1931b5a28bcac7a41bdb1eaddcb1223eeec/TSC/Sources/TSCBasic/OrderedSet.swift /// An ordered set is an ordered collection of instances of `Element` in which /// uniqueness of the objects is guaranteed. -public struct OrderedSet: Equatable, RangeReplaceableCollection { +public struct OrderedSet: Equatable, RangeReplaceableCollection, SetAlgebra { public typealias Element = E public typealias Index = Int public typealias Indices = Range @@ -88,11 +88,29 @@ public struct OrderedSet: Equatable, RangeReplaceableCollection { Collection.Element == Element, Range.Bound == Index { - let newElementsOrderedSet = OrderedSet(newElements) - self.array.replaceSubrange(subrange, with: newElementsOrderedSet) + let t = newElements.filter { newElement in + !zip(self.array.indices, self.array).contains { index, element in + if subrange.contains(index) { + return false + } + return element == newElement + } + } + self.array.replaceSubrange( + subrange, + with: t + ) self.set.formUnion(newElements) } + public mutating func insert(_ newMember: E) -> (inserted: Bool, memberAfterInsert: E) { + let result = self.set.insert(newMember) + if result.inserted { + self.array.append(newMember) + } + return result + } + /// Remove and return the element at the beginning of the ordered set. public mutating func removeFirst() -> Element { let firstElement = self.array.removeFirst() @@ -127,6 +145,49 @@ public struct OrderedSet: Equatable, RangeReplaceableCollection { public static func == (lhs: OrderedSet, rhs: OrderedSet) -> Bool { lhs.contents == rhs.contents } + + public func union(_ other: OrderedSet) -> OrderedSet { + var this = self + this.formUnion(other) + return this + } + + public func intersection(_ other: OrderedSet) -> OrderedSet { + var this = self + this.formIntersection(other) + return this + } + + public func symmetricDifference(_ other: OrderedSet) -> OrderedSet { + var this = self + this.formSymmetricDifference(other) + return this + } + + public mutating func update(with newMember: E) -> E? { + if let index = self.array.firstIndex(where: { $0 == newMember }) { + self.array[index] = newMember + } else { + self.array.append(newMember) + } + return self.set.update(with: newMember) + } + + public mutating func formUnion(_ other: OrderedSet) { + self.array += other.filter { !self.set.contains($0) } + self.set.formUnion(other) + } + + public mutating func formIntersection(_ other: OrderedSet) { + self.set.formIntersection(other) + self.array.removeAll { !self.set.contains($0) } + } + + public mutating func formSymmetricDifference(_ other: OrderedSet) { + self.array += other.filter { !self.set.contains($0) } + self.set.formSymmetricDifference(other) + self.array.removeAll { !self.set.contains($0) } + } } extension OrderedSet: ExpressibleByArrayLiteral { @@ -167,3 +228,9 @@ extension OrderedSet: Encodable where Element: Encodable { try self.array.encode(to: encoder) } } + +extension OrderedSet: CustomStringConvertible { + public var description: String { + self.array.description + } +} From 1afdfd54e26895f305a7b8d2c08e3904b5f509fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 16:09:09 -0400 Subject: [PATCH 45/89] test(ordered-set): add tests for OrderedSet --- Tests/FueledUtils.xcodeproj/project.pbxproj | 4 ++ Tests/Tests/CoalescingActionSpec.swift | 1 - Tests/Tests/OrderedSetSpec.swift | 67 +++++++++++++++++++ Tests/Tests/ReactiveSwiftExtensionsSpec.swift | 1 - 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 Tests/Tests/OrderedSetSpec.swift diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 47cb1822..50cafb87 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */; }; F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; + F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +28,7 @@ F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSetSpec.swift; sourceTree = ""; }; F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; /* End PBXFileReference section */ @@ -99,6 +101,7 @@ children = ( F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, + F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */, ); path = Tests; sourceTree = ""; @@ -233,6 +236,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */, F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */, F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, ); diff --git a/Tests/Tests/CoalescingActionSpec.swift b/Tests/Tests/CoalescingActionSpec.swift index 3bd7dd5d..f2008203 100644 --- a/Tests/Tests/CoalescingActionSpec.swift +++ b/Tests/Tests/CoalescingActionSpec.swift @@ -16,7 +16,6 @@ import Quick import Nimble import FueledUtils import ReactiveSwift -import XCTest class CoalescingActionSpec: QuickSpec { override func spec() { diff --git a/Tests/Tests/OrderedSetSpec.swift b/Tests/Tests/OrderedSetSpec.swift new file mode 100644 index 00000000..e02f7032 --- /dev/null +++ b/Tests/Tests/OrderedSetSpec.swift @@ -0,0 +1,67 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Quick +import Nimble +import FueledUtils +import XCTest + +class OrderedSetSpec: QuickSpec { + override func spec() { + describe("OrderedSet") { + describe("Initialization") { + it("should initialize with no corruption") { + expect(OrderedSet([1])) == [1] + } + it("should initialize with no corruption") { + expect(OrderedSet([1, 1, 2, 2, 4, 3, 1, 2, 3])) == [1, 2, 4, 3] + } + } + describe("Array operations") { + it("should add properly") { + var set = OrderedSet([1, 2, 3]) + set += [2, 2, 3, 3, 4, 4] + expect(set) == [1, 2, 3, 4] + } + it("should remove properly") { + var set = OrderedSet([1, 2, 3]) + set.remove(2) + expect(set) == [1, 3] + } + it("should replace in a subrange properly") { + var set = OrderedSet([1, 2, 3, 4, 5, 6, 7]) + set.replaceSubrange(1..<4, with: [3, 5, 9, 8, 1, 0]) + expect(set) == [1, 3, 9, 8, 1, 0, 5, 6, 7] + } + } + describe("Set algebra") { + it("should substract properly") { + var set = OrderedSet([1, 2, 3]) + set.subtract([2, 3, 4]) + expect(set) == [1] + } + it("should form an union properly") { + var set = OrderedSet([1, 2, 3]) + set.formUnion([3, 2, 4, 1]) + expect(set) == [1, 2, 3, 4] + } + it("should perform a symmetric difference properly") { + var set: Set = [1, 2, 3, 4] + set.formSymmetricDifference([3, 4, 5]) + expect(set) == [1, 2, 5] + } + } + } + } +} diff --git a/Tests/Tests/ReactiveSwiftExtensionsSpec.swift b/Tests/Tests/ReactiveSwiftExtensionsSpec.swift index 273ac5d8..fd9f5580 100644 --- a/Tests/Tests/ReactiveSwiftExtensionsSpec.swift +++ b/Tests/Tests/ReactiveSwiftExtensionsSpec.swift @@ -16,7 +16,6 @@ import Quick import Nimble import FueledUtils import ReactiveSwift -import XCTest class ReactiveSwiftExtensionsSpec: QuickSpec { override func spec() { From f9b5bf0e2469c4753edbba03b868c786dcf41771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 7 Oct 2020 16:15:41 -0400 Subject: [PATCH 46/89] chore(scheme): rename schemes --- .../FueledUtilsTests-SwiftUI.xcscheme | 70 ------------------- ...ift.xcscheme => FueledUtilsTests.xcscheme} | 0 2 files changed, 70 deletions(-) delete mode 100644 Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme rename Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/{FueledUtilsTests-ReactiveSwift.xcscheme => FueledUtilsTests.xcscheme} (100%) diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme deleted file mode 100644 index db2d8d6c..00000000 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-SwiftUI.xcscheme +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme similarity index 100% rename from Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests-ReactiveSwift.xcscheme rename to Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme From 1a6e03f1bc9eac25591e85716b729b456c17c09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 14 Oct 2020 13:54:15 -0400 Subject: [PATCH 47/89] chore(codeowners): move code owners file to the right directory --- .github/CODEOWNERS | 1 + Tests/.github/CODEOWNERS | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS delete mode 100644 Tests/.github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..ab5e09e3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @stephanecopin @metatheoretic @heymansmile @notbenoit diff --git a/Tests/.github/CODEOWNERS b/Tests/.github/CODEOWNERS deleted file mode 100644 index 68ba5749..00000000 --- a/Tests/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @stephanecopin @heymansmile @notbenoit From 1061417f76ce10a3ae66a20e5ff65c3784f25be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 15 Oct 2020 14:01:25 -0400 Subject: [PATCH 48/89] chore(code-coverage): enable code coverage & add codecov.yml --- .github/codecov.yml | 3 +++ .../xcschemes/FueledUtilsTests.xcscheme | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..51e2f5ee --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "Pods" + - "Tests" diff --git a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme index 4fe5714e..f2a5dc09 100644 --- a/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme +++ b/Tests/FueledUtils.xcodeproj/xcshareddata/xcschemes/FueledUtilsTests.xcscheme @@ -10,7 +10,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + From 29be1b6c16dbee4c23e51e5fe58b61e73aa3282c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 16 Oct 2020 12:33:30 -0400 Subject: [PATCH 49/89] chore(changelog): add changelog & Dangerfile --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++ Dangerfile | 15 +++++++++++++ Gemfile | 8 +++++++ Gemfile.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 Dangerfile create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4ee8044e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +## Master + +##### New Features/Enhancements + +- Add an optional `insets` parameter to `addAndFitSubview()` + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +- Make `removeArrangedSubviews()`'s `removeFromHierachy` parameter default to `true` + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +- Add `tapped` helper to link any `ReactiveActionProtocol` to any `UIControl` + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +- Add `AnyIdentifiable` & `AnyAction` for type-erased `Identifiable` & `ReactiveActionProtocol` respectively + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +- Add `OverridingAction`, a new `Action` that if executed when already executing, will cancel the previous producer and start a new one + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +- Make `OrderedSet` conform to `SetAlgebra` + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) + +##### Bug Fixes + +- Fix an internal state corruption issue in `OrderedSet` + [Stéphane Copin](https://github.com/stephanecopin) + [#53](https://github.com/Fueled/ios-utilities/pull/53) diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 00000000..2a58fe91 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +has_app_changes = !git.modified_files.grep(/FueledUtils/).empty? +if !git.modified_files.include?('CHANGELOG.md') && has_app_changes + warn("Please include a CHANGELOG entry to credit yourself! \nYou can find it at [CHANGELOG.md](https://github.com/Fueled/ios-utilities/blob/develop/CHANGELOG.md).", :sticky => false) + markdown <<-MARKDOWN +Here's an example of your CHANGELOG entry: +```markdown +- #{github.pr_title}\s\s + [#{github.pr_author}](https://github.com/#{github.pr_author}) + [#pull_request_number](https://github.com/Fueled/ios-utilities/pulls/pull_request_number) +``` +*note*: There are two invisible spaces after the entry's text. +MARKDOWN +end diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..cb45b4ed --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'danger' + diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..04b9318a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,61 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + claide (1.0.3) + claide-plugins (0.9.2) + cork + nap + open4 (~> 1.3) + colored2 (3.1.2) + cork (0.3.0) + colored2 (~> 3.1) + danger (8.0.6) + claide (~> 1.0) + claide-plugins (>= 0.9.2) + colored2 (~> 3.1) + cork (~> 0.1) + faraday (>= 0.9.0, < 2.0) + faraday-http-cache (~> 2.0) + git (~> 1.7) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.0) + no_proxy_fix + octokit (~> 4.7) + terminal-table (~> 1) + faraday (1.0.1) + multipart-post (>= 1.2, < 3) + faraday-http-cache (2.2.0) + faraday (>= 0.8) + git (1.7.0) + rchardet (~> 1.8) + kramdown (2.3.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + multipart-post (2.1.1) + nap (1.1.0) + no_proxy_fix (0.1.2) + octokit (4.18.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) + open4 (1.3.4) + public_suffix (4.0.6) + rchardet (1.8.0) + rexml (3.2.4) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + unicode-display_width (1.7.0) + +PLATFORMS + ruby + +DEPENDENCIES + danger + +BUNDLED WITH + 2.1.4 From c474eb1b51e463fc16172807c9038998c3f4c2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 14 Oct 2020 13:58:36 -0400 Subject: [PATCH 50/89] chore(reactive): rename existing files in preparation for new combine helpers --- .../{AnyAction.swift => ReactiveAnyAction.swift} | 2 +- ...Action.swift => ReactiveCoalescingAction.swift} | 0 ...Action.swift => ReactiveOverridingAction.swift} | 2 +- ....swift => ReactiveControlProtocol+Tapped.swift} | 10 +++++----- .../{TapAction.swift => ReactiveTapAction.swift} | 14 +++++++------- .../ControlProtocol.swift | 4 ++-- Tests/FueledUtils.xcodeproj/project.pbxproj | 8 ++++---- ...ec.swift => ReactiveCoalescingActionSpec.swift} | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) rename FueledUtils/ReactiveSwift/{AnyAction.swift => ReactiveAnyAction.swift} (96%) rename FueledUtils/ReactiveSwift/{InputCoalescingAction.swift => ReactiveCoalescingAction.swift} (100%) rename FueledUtils/ReactiveSwift/{OverridingAction.swift => ReactiveOverridingAction.swift} (97%) rename FueledUtils/ReactiveSwiftUIKit/{ControlProtocol+Tapped.swift => ReactiveControlProtocol+Tapped.swift} (85%) rename FueledUtils/ReactiveSwiftUIKit/{TapAction.swift => ReactiveTapAction.swift} (80%) rename FueledUtils/{ReactiveSwiftUIKit => UIKit}/ControlProtocol.swift (89%) rename Tests/Tests/{CoalescingActionSpec.swift => ReactiveCoalescingActionSpec.swift} (94%) diff --git a/FueledUtils/ReactiveSwift/AnyAction.swift b/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift similarity index 96% rename from FueledUtils/ReactiveSwift/AnyAction.swift rename to FueledUtils/ReactiveSwift/ReactiveAnyAction.swift index 2d43590a..907f2960 100644 --- a/FueledUtils/ReactiveSwift/AnyAction.swift +++ b/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift @@ -19,7 +19,7 @@ import ReactiveSwift /// A type-erased Action that allows to store any `ReactiveActionProtocol` /// (loosing any type information at the same time) /// -final class AnyAction: ReactiveActionProtocol { +final class ReactiveAnyAction: ReactiveActionProtocol { private let applyClosure: (Input) -> SignalProducer private let deinitToken: Lifetime.Token diff --git a/FueledUtils/ReactiveSwift/InputCoalescingAction.swift b/FueledUtils/ReactiveSwift/ReactiveCoalescingAction.swift similarity index 100% rename from FueledUtils/ReactiveSwift/InputCoalescingAction.swift rename to FueledUtils/ReactiveSwift/ReactiveCoalescingAction.swift diff --git a/FueledUtils/ReactiveSwift/OverridingAction.swift b/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift similarity index 97% rename from FueledUtils/ReactiveSwift/OverridingAction.swift rename to FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift index 7c1bdc0e..497a20ae 100644 --- a/FueledUtils/ReactiveSwift/OverridingAction.swift +++ b/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift @@ -19,7 +19,7 @@ import ReactiveSwift /// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, /// and will be interrupt the previous apply(). /// -public final class OverridingAction: ReactiveActionProtocol { +public final class ReactiveOverridingAction: ReactiveActionProtocol { private let action: ReactiveSwift.Action private var observer: Signal.Observer? diff --git a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift similarity index 85% rename from FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift rename to FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift index 36cfe991..0b366203 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol+Tapped.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift @@ -25,7 +25,7 @@ extension Reactive where Base: ControlProtocol { /// protocol to represents the button rather than hardcode it to classes, /// allowing for any `UIControl` to use this method. /// - var tapped: TapAction? { + var tapped: ReactiveTapAction? { get { self.tapActionStorage?.tapAction } @@ -42,8 +42,8 @@ extension Reactive where Base: ControlProtocol { (control as! ControlLoadingProtocol).isLoading = isExecuting } <~ newValue.isExecuting } - self.base.removeTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) - self.base.addTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) + self.base.removeTarget(newValue, action: ReactiveTapAction.selector, for: .primaryActionTriggered) + self.base.addTarget(newValue, action: ReactiveTapAction.selector, for: .primaryActionTriggered) self.tapActionStorage = tapActionStorage } } @@ -60,10 +60,10 @@ extension Reactive where Base: ControlProtocol { } private final class TapActionStorage { - let tapAction: TapAction + let tapAction: ReactiveTapAction let disposable = ScopedDisposable(CompositeDisposable()) - init(_ tapAction: TapAction) { + init(_ tapAction: ReactiveTapAction) { self.tapAction = tapAction } } diff --git a/FueledUtils/ReactiveSwiftUIKit/TapAction.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift similarity index 80% rename from FueledUtils/ReactiveSwiftUIKit/TapAction.swift rename to FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift index 3b47b1eb..782dcd59 100644 --- a/FueledUtils/ReactiveSwiftUIKit/TapAction.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift @@ -16,19 +16,19 @@ import FueledUtils import ReactiveSwift /// -/// TapAction wraps a `ReactiveActionProtocol` for use by any `ButtonProtocol` +/// `ReactiveTapAction` wraps a `ReactiveActionProtocol` for use by any `ButtonProtocol` /// This is a mirror of `CococaAction` in `ReactiveCocoa`, allowing to use a /// `ButtonProtocol` and assigin /// -final class TapAction: NSObject { +final class ReactiveTapAction: NSObject { @objc static var selector: Selector { - #selector(userDidTapButton(_:)) + #selector(userDidTapControl(_:)) } let isExecuting: Property let isEnabled: Property - private let executeClosure: (Button) -> Void + private let executeClosure: (Control) -> Void convenience init(_ action: Action) where Action.Input == Void { self.init(action, input: ()) @@ -38,7 +38,7 @@ final class TapAction: NSObject { self.init(action) { _ in input } } - init(_ action: Action, inputTransform: @escaping (Button) -> Action.Input) { + init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { self.executeClosure = { action.apply(inputTransform($0)).start() } @@ -53,7 +53,7 @@ final class TapAction: NSObject { ) } - @objc private func userDidTapButton(_ button: Any) { - self.executeClosure(button as! Button) + @objc private func userDidTapControl(_ button: Any) { + self.executeClosure(button as! Control) } } diff --git a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift b/FueledUtils/UIKit/ControlProtocol.swift similarity index 89% rename from FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift rename to FueledUtils/UIKit/ControlProtocol.swift index 0de3ffde..261f9af6 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ControlProtocol.swift +++ b/FueledUtils/UIKit/ControlProtocol.swift @@ -17,13 +17,13 @@ import UIKit /// /// A protocol that represents a control, which must be a `UIControl`. /// -protocol ControlProtocol: UIControl { +public protocol ControlProtocol: UIControl { } /// /// A protocol that represents a control with a `isLoading` property. /// -protocol ControlLoadingProtocol: ControlProtocol { +public protocol ControlLoadingProtocol: ControlProtocol { var isLoading: Bool { get set } } diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 50cafb87..f273d675 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */; }; - F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */; }; + F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; /* End PBXBuildFile section */ @@ -24,7 +24,7 @@ D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; + F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -99,7 +99,7 @@ F463C739241835DD000A0B29 /* Tests */ = { isa = PBXGroup; children = ( - F463C73A241835DD000A0B29 /* CoalescingActionSpec.swift */, + F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */, ); @@ -237,7 +237,7 @@ buildActionMask = 2147483647; files = ( F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */, - F463C73C241835DD000A0B29 /* CoalescingActionSpec.swift in Sources */, + F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */, F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tests/Tests/CoalescingActionSpec.swift b/Tests/Tests/ReactiveCoalescingActionSpec.swift similarity index 94% rename from Tests/Tests/CoalescingActionSpec.swift rename to Tests/Tests/ReactiveCoalescingActionSpec.swift index f2008203..3a9c0d68 100644 --- a/Tests/Tests/CoalescingActionSpec.swift +++ b/Tests/Tests/ReactiveCoalescingActionSpec.swift @@ -17,9 +17,9 @@ import Nimble import FueledUtils import ReactiveSwift -class CoalescingActionSpec: QuickSpec { +class ReactiveCoalescingActionSpec: QuickSpec { override func spec() { - describe("CoalescingAction") { + describe("ReactiveCoalescingAction") { describe("apply.dispose()") { it("should dispose of all created signal producers") { var startCounter = 0 From c7546c0497774aa4b38b02c9e36013d593146b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 14 Oct 2020 14:02:33 -0400 Subject: [PATCH 51/89] feat(atomic): now allow to return a value when using withValue() or modify() in Atomic --- FueledUtils/Core/Atomic.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift index 3d11e256..0fb9070a 100644 --- a/FueledUtils/Core/Atomic.swift +++ b/FueledUtils/Core/Atomic.swift @@ -28,20 +28,20 @@ public final class AtomicValue { atomicValue.modify { $0 = value } } - public func modify(_ modify: (inout Value) -> Void) { + public func modify(_ modify: (inout Value) -> Return) -> Return { self.lock.lock() defer { self.lock.unlock() } - modify(&self.value) + return modify(&self.value) } - public func withValue(_ getter: (Value) -> Void) { + public func withValue(_ getter: (Value) -> Return) -> Return { self.lock.lock() defer { self.lock.unlock() } - getter(self.value) + return getter(self.value) } } From 5a5d5cafd2ba66c8729510c5a5c138605cf78fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 14 Oct 2020 14:03:48 -0400 Subject: [PATCH 52/89] feat(combine): add many combine helpers --- FueledUtils/Combine/Action.swift | 20 +-- FueledUtils/Combine/ActionProtocol.swift | 103 ++++++++++++ FueledUtils/Combine/AnyAction.swift | 64 ++++++++ ...r.swift => AnyCurrentValuePublisher.swift} | 0 FueledUtils/Combine/CoalescingAction.swift | 149 ++++++++++++++++++ .../CombineExtensions+Cancellables.swift | 32 ++++ FueledUtils/Combine/CombineExtensions.swift | 32 ++++ .../Combine/NSObject+CombineExtensions.swift | 18 +++ FueledUtils/Combine/OverridingAction.swift | 101 ++++++++++++ .../Publisher+AdditionalHandleEvents.swift | 93 +++++++++++ FueledUtils/Combine/PublisherExtensions.swift | 65 +++++++- Tests/FueledUtils.xcodeproj/project.pbxproj | 4 + Tests/Tests/CoalescingActionSpec.swift | 58 +++++++ 13 files changed, 728 insertions(+), 11 deletions(-) create mode 100644 FueledUtils/Combine/ActionProtocol.swift create mode 100644 FueledUtils/Combine/AnyAction.swift rename FueledUtils/Combine/{CurrentValuePublisher.swift => AnyCurrentValuePublisher.swift} (100%) create mode 100644 FueledUtils/Combine/CoalescingAction.swift create mode 100644 FueledUtils/Combine/CombineExtensions+Cancellables.swift create mode 100644 FueledUtils/Combine/CombineExtensions.swift create mode 100644 FueledUtils/Combine/NSObject+CombineExtensions.swift create mode 100644 FueledUtils/Combine/OverridingAction.swift create mode 100644 FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift create mode 100644 Tests/Tests/CoalescingActionSpec.swift diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 022bca3f..6e97dcd9 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -63,16 +63,18 @@ public final class Action { receiveOutput: { value in values.send(value) }, - receiveCompletion: { [weak action] completion in - isExecutingLock.lock() - action?.isExecuting = false - isExecutingLock.unlock() + receiveCompletion: { completion in switch completion { case .finished: break case .failure(let error): errors.send(error) } + }, + receiveTermination: { [weak action] in + isExecutingLock.lock() + action?.isExecuting = false + isExecutingLock.unlock() } ) .mapError { .failure($0) } @@ -115,12 +117,6 @@ public final class Action { } } -extension Action where Input == Void { - public func apply() -> AnyPublisher> { - self.apply(()) - } -} - extension Publisher where Failure: ActionErrorProtocol { public func unwrappingActionError() -> AnyPublisher { self.catch { actionError -> AnyPublisher in @@ -145,6 +141,7 @@ extension Action { // Please note that the actions created with the `mapXxx` family are interweaved together - starting one // will update the other, and vice versa. // For example, on use case is to type-erase an Action. + @available(*, deprecated, message: "Use `AnyAction` instead") public func mapInput(_ mapper: @escaping (NewInput) -> Input) -> Action { self.mapAll( mapInput: mapper, @@ -153,6 +150,7 @@ extension Action { ) } + @available(*, deprecated, message: "Use `AnyAction` instead") public func map(_ mapper: @escaping (Output) -> NewOutput) -> Action { self.mapAll( mapInput: { $0 }, @@ -161,6 +159,7 @@ extension Action { ) } + @available(*, deprecated, message: "Use `AnyAction` instead") public func mapError(_ mapper: @escaping (Failure) -> NewFailure) -> Action { self.mapAll( mapInput: { $0 }, @@ -169,6 +168,7 @@ extension Action { ) } + @available(*, deprecated, message: "Use `AnyAction` instead") public func mapAll( mapInput: @escaping (NewInput) -> (Input), map: @escaping (Output) -> (NewOutput), diff --git a/FueledUtils/Combine/ActionProtocol.swift b/FueledUtils/Combine/ActionProtocol.swift new file mode 100644 index 00000000..ceccbb24 --- /dev/null +++ b/FueledUtils/Combine/ActionProtocol.swift @@ -0,0 +1,103 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine + +public protocol ActionProtocol { + /// + /// The type of the values used as inputs to the action. + /// + associatedtype Input + /// + /// + /// The type of the values output from the action. + /// + associatedtype Output + /// The type of errors emitted by the action. + /// + associatedtype Failure: Swift.Error + /// + /// The type of errors emitted when applying the action. + /// + associatedtype ApplyFailure: Swift.Error + + associatedtype IsExecutingPublisher: Publisher where IsExecutingPublisher.Output == Bool, IsExecutingPublisher.Failure == Never + associatedtype IsEnabledPublisher: Publisher where IsEnabledPublisher.Output == Bool, IsEnabledPublisher.Failure == Never + + associatedtype ValuesPublisher: Publisher where ValuesPublisher.Output == Output, ValuesPublisher.Failure == Never + associatedtype ErrorsPublisher: Publisher where ErrorsPublisher.Output == Failure, ErrorsPublisher.Failure == Never + + associatedtype ApplyPublisher: Publisher where ApplyPublisher.Output == Output, ApplyPublisher.Failure == ApplyFailure + + /// + /// Whether the action is currently executing. + /// + var isExecuting: Bool { get } + /// + /// Whether the action is currently enabled. + /// + var isEnabled: Bool { get } + /// + /// Whether the action is currently executing. + /// + var isExecutingPublisher: IsExecutingPublisher { get } + /// + /// Whether the action is currently enabled. + /// + var isEnabledPublisher: IsEnabledPublisher { get } + + var values: ValuesPublisher { get } + var errors: ErrorsPublisher { get } + + /// + /// Create a `SignalProducer` that would attempt to create and start a unit of work of + /// the `Action`. The `SignalProducer` would forward only events generated by the unit + /// of work it created. + /// + /// - Parameters: + /// - input: A value to be used to create the unit of work. + /// + /// - Returns: A producer that forwards events generated by its started unit of work, + /// or returns an appropriate `ApplyError` indicating the specific error + /// that happened. + /// + func apply(_ input: Input) -> ApplyPublisher +} + +extension Action: ActionProtocol { + public typealias ApplyFailure = ActionError + + public var isExecutingPublisher: AnyPublisher { + self.$isExecuting.eraseToAnyPublisher() + } + + public var isEnabledPublisher: AnyPublisher { + self.$isEnabled.eraseToAnyPublisher() + } +} + +extension ActionProtocol where Input == Void { + /// + /// Create a `SignalProducer` that would attempt to create and start a unit of work of + /// the `Action`. The `SignalProducer` would forward only events generated by the unit + /// of work it created. + /// + /// - Returns: A producer that forwards events generated by its started unit of work, + /// or returns an appropriate `ApplyError` indicating the specific error + /// that happened. + /// + func apply() -> ApplyPublisher { + return self.apply(()) + } +} diff --git a/FueledUtils/Combine/AnyAction.swift b/FueledUtils/Combine/AnyAction.swift new file mode 100644 index 00000000..26db719c --- /dev/null +++ b/FueledUtils/Combine/AnyAction.swift @@ -0,0 +1,64 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine + +/// +/// A type-erased Action that allows to store any `ActionProtocol` +/// (loosing any type information at the same time) +/// +public final class AnyAction: ActionProtocol { + public typealias Input = Any + public typealias Output = Any + public typealias Failure = Error + public typealias IsExecutingPublisher = AnyPublisher + public typealias IsEnabledPublisher = AnyPublisher + public typealias ValuesPublisher = AnyPublisher + public typealias ErrorsPublisher = AnyPublisher + public typealias ApplyPublisher = AnyPublisher + public typealias ApplyFailure = Error + + private let applyClosure: (Any) -> AnyPublisher + private var cancellables = Set() + + public init(_ action: Action) { + self.isEnabled = action.isEnabled + self.isExecuting = action.isExecuting + self.applyClosure = { action.apply($0 as! Action.Input).map { $0 }.mapError { $0 }.eraseToAnyPublisher() } + self.values = action.values.map { $0 }.eraseToAnyPublisher() + self.errors = action.errors.map { $0 }.eraseToAnyPublisher() + action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) + .store(in: &self.cancellables) + } + + @Published public private(set) var isEnabled: Bool + @Published public private(set) var isExecuting: Bool + + public var isEnabledPublisher: AnyPublisher { + self.$isEnabled.eraseToAnyPublisher() + } + + public var isExecutingPublisher: AnyPublisher { + self.$isExecuting.eraseToAnyPublisher() + } + + public let values: AnyPublisher + public let errors: AnyPublisher + + public func apply(_ input: Any) -> AnyPublisher { + self.applyClosure(input) + } +} diff --git a/FueledUtils/Combine/CurrentValuePublisher.swift b/FueledUtils/Combine/AnyCurrentValuePublisher.swift similarity index 100% rename from FueledUtils/Combine/CurrentValuePublisher.swift rename to FueledUtils/Combine/AnyCurrentValuePublisher.swift diff --git a/FueledUtils/Combine/CoalescingAction.swift b/FueledUtils/Combine/CoalescingAction.swift new file mode 100644 index 00000000..f8abd0f0 --- /dev/null +++ b/FueledUtils/Combine/CoalescingAction.swift @@ -0,0 +1,149 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine + +/// +/// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, +/// and will be completed with the same output when the initial executing action completes. +/// Disposing any of the `AnyPublisher` returned by `apply()` will cancel the action. +/// +/// The `Input` is used when sending inputs to the **initial** `apply()` call - for subsequent +/// calls to `apply()` when the action is executing, the inputs will be ignored until +/// the action terminates. +/// +public class CoalescingAction: ActionProtocol { + public typealias ApplyFailure = Failure + + private let action: Action + private var passthroughSubject: PassthroughSubject? + private var cancellableContainer: CancellableContainer? + + private class CancellableContainer { + private let cancellable: AnyCancellable + private let count = AtomicValue(0) + + init(_ cancellable: AnyCancellable) { + self.cancellable = cancellable + } + + func add(_ passthroughSubject: PassthroughSubject) -> ((Bool) -> Void) -> Void { + self.count.modify { $0 += 1 } + return { isFinal in + let isCancelled = self.count.modify { count -> Bool in + count -= 1 + let isCancelled = count == 0 + if isCancelled { + self.cancellable.cancel() + } + return isCancelled + } + isFinal(isCancelled) + } + } + } + + @Published public private(set) var isExecuting: Bool + @Published public private(set) var isEnabled: Bool + + public var isExecutingPublisher: AnyPublisher { + self.$isExecuting.eraseToAnyPublisher() + } + + public var isEnabledPublisher: AnyPublisher { + self.$isEnabled.eraseToAnyPublisher() + } + + public var values: AnyPublisher { + self.action.values + } + + public var errors: AnyPublisher { + self.action.errors + } + + private var cancellables = Set([]) + + /// + /// Initializes a `CoalescingAction`. + /// + /// When the `Action` is asked to start the execution with an input value, a unit of + /// work — represented by a `Publisher` — would be created by invoking + /// `execute` with the input value. + /// + /// - parameters: + /// - execute: A closure that produces a unit of work, as `Publisher`, to be + /// executed by the `Action`. + /// + public init( + execute: @escaping (Input) -> ExecutePublisher + ) where ExecutePublisher.Output == Output, ExecutePublisher.Failure == Failure { + self.action = Action(execute: execute) + self.isEnabled = self.action.isEnabled + self.isExecuting = self.action.isExecuting + self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) + .store(in: &self.cancellables) + } + + /// + /// Create a `AnyPublisher` that would attempt to create and start a unit of work of + /// the `Action`. The `AnyPublisher` would forward only events generated by the unit + /// of work it created. + /// + /// - Warning: Only the first call to `apply()` when the action's `isExecuting`'s `value` is `false` will be using its parameters. + /// Subsequent calls when the action is already executing will ignore the input. + /// + /// - Parameters: + /// - input: The initial input to use for the action. + /// + /// - Returns: A publisher that forwards events generated by its started unit of work. If the action was already executing, it will create a `AnyPublisher` + /// that will forward the events of the initially created `AnyPublisher`. + /// + public func apply(_ input: Input) -> AnyPublisher { + let passthroughSubject = PassthroughSubject() + var cancellable: AnyCancellable! + let cancellableContainer: CancellableContainer + let originalPassthroughSubject: PassthroughSubject + if let existingPassthroughSubject = self.passthroughSubject { + assert(self.isExecuting, "Action must be executing when `passthroughSubject` is non-nil") + originalPassthroughSubject = existingPassthroughSubject + cancellableContainer = self.cancellableContainer! + } else { + originalPassthroughSubject = PassthroughSubject() + self.passthroughSubject = originalPassthroughSubject + cancellable = self.action.apply(input) + .unwrappingActionError() + .subscribe(originalPassthroughSubject) + cancellableContainer = CancellableContainer(cancellable) + self.cancellableContainer = cancellableContainer + } + cancellable = originalPassthroughSubject.subscribe(passthroughSubject) + let onCompletion = cancellableContainer.add(passthroughSubject) + return passthroughSubject + .handleEvents( + receiveTermination: { [weak self] in + onCompletion() { isCancelled in + if isCancelled { + self?.passthroughSubject = nil + self?.cancellableContainer = nil + } + } + cancellable = nil + } + ) + .eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/CombineExtensions+Cancellables.swift b/FueledUtils/Combine/CombineExtensions+Cancellables.swift new file mode 100644 index 00000000..72f73e04 --- /dev/null +++ b/FueledUtils/Combine/CombineExtensions+Cancellables.swift @@ -0,0 +1,32 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine + +private var cancellablesKey: UInt8 = 0 + +extension CombineExtensions { + public var cancellables: Set { + get { + objc_getAssociatedObject(self.base, &cancellablesKey) as? Set ?? { + let cancellables = Set() + self.cancellables = cancellables + return cancellables + }() + } + set { + objc_setAssociatedObject(self.base, &cancellablesKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY) + } + } +} diff --git a/FueledUtils/Combine/CombineExtensions.swift b/FueledUtils/Combine/CombineExtensions.swift new file mode 100644 index 00000000..54a6899f --- /dev/null +++ b/FueledUtils/Combine/CombineExtensions.swift @@ -0,0 +1,32 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 + +public protocol CombineExtensionsProvider { +} + +public final class CombineExtensions { + var base: Base + + init(_ base: Base) { + self.base = base + } +} + +extension CombineExtensionsProvider { + public var combineExtensions: CombineExtensions { + return CombineExtensions(self) + } +} diff --git a/FueledUtils/Combine/NSObject+CombineExtensions.swift b/FueledUtils/Combine/NSObject+CombineExtensions.swift new file mode 100644 index 00000000..fbf89850 --- /dev/null +++ b/FueledUtils/Combine/NSObject+CombineExtensions.swift @@ -0,0 +1,18 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 + +extension NSObject: CombineExtensionsProvider { +} diff --git a/FueledUtils/Combine/OverridingAction.swift b/FueledUtils/Combine/OverridingAction.swift new file mode 100644 index 00000000..0a1bdc43 --- /dev/null +++ b/FueledUtils/Combine/OverridingAction.swift @@ -0,0 +1,101 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine + +/// +/// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, +/// and will be interrupt the previous apply(). +/// +public class OverridingAction: ActionProtocol { + public typealias ApplyFailure = Failure + + private let action: Action + private var passthroughSubject: PassthroughSubject? + private var currentCancellable: AnyCancellable? + + @Published public private(set) var isExecuting: Bool + @Published public private(set) var isEnabled: Bool + + public var isExecutingPublisher: AnyPublisher { + self.$isExecuting.eraseToAnyPublisher() + } + + public var isEnabledPublisher: AnyPublisher { + self.$isEnabled.eraseToAnyPublisher() + } + + public var values: AnyPublisher { + self.action.values + } + + public var errors: AnyPublisher { + self.action.errors + } + + private var cancellables = Set([]) + + /// + /// Initializes a `CoalescingAction`. + /// + /// When the `Action` is asked to start the execution with an input value, a unit of + /// work — represented by a `Publisher` — would be created by invoking + /// `execute` with the input value. + /// + /// - parameters: + /// - execute: A closure that produces a unit of work, as `Publisher`, to be + /// executed by the `Action`. + /// + public init( + execute: @escaping (Input) -> ExecutePublisher + ) where ExecutePublisher.Output == Output, ExecutePublisher.Failure == Failure { + self.action = Action(execute: execute) + self.isEnabled = self.action.isEnabled + self.isExecuting = self.action.isExecuting + self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) + .store(in: &self.cancellables) + } + + /// + /// Create a `AnyPublisher` that would attempt to create and start a unit of work of + /// the `Action`. The `AnyPublisher` would forward only events generated by the unit + /// of work it created. + /// + /// - Warning: Only the first call to `apply()` when the action's `isExecuting`'s `value` is `false` will be using its parameters. + /// Subsequent calls when the action is already executing will ignore the input. + /// + /// - Parameters: + /// - input: The initial input to use for the action. + /// + /// - Returns: A publisher that forwards events generated by its started unit of work. If the action was already executing, it will create a `AnyPublisher` + /// that will forward the events of the initially created `AnyPublisher`. + /// + public func apply(_ input: Input) -> AnyPublisher { + let passthroughSubject = PassthroughSubject() + self.currentCancellable = nil + self.currentCancellable = self.action.apply(input) + .unwrappingActionError() + .subscribe(passthroughSubject) + self.passthroughSubject = passthroughSubject + return passthroughSubject + .handleEvents( + receiveTermination: { [weak self] in + self?.currentCancellable = nil + } + ) + .eraseToAnyPublisher() + } +} diff --git a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift new file mode 100644 index 00000000..56e9137e --- /dev/null +++ b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift @@ -0,0 +1,93 @@ +// +// Publisher+AdditionalHandleEvents.swift +// FueledUtils +// +// Created by Stéphane Copin on 10/14/20. +// + +import Combine + +extension Publisher { + /// - Parameters: + /// - receiveTermination: Sent when the publisher either a completion event or is cancelled. + /// - receiveResult: Sent when the publisher send values, or an error. + /// - Please refer to the documentation for + /// `Publisher.self.handleEvents(receiveSubscription:receiveOutput:receiveCompletion:receiveCancel:receiveRequest:)` + /// for more information about the other parameters. + public func handleEvents( + receiveSubscription: ((Subscription) -> Void)? = nil, + receiveOutput: ((Output) -> Void)? = nil, + receiveCompletion: ((Subscribers.Completion) -> Void)? = nil, + receiveCancel: (() -> Void)? = nil, + receiveTermination: @escaping () -> Void, + receiveResult: ((Result) -> Void)? = nil, + receiveRequest: ((Subscribers.Demand) -> Void)? = nil + ) -> Publishers.HandleEvents { + self.extendedHandleEvents( + receiveSubscription: receiveSubscription, + receiveOutput: receiveOutput, + receiveCompletion: receiveCompletion, + receiveCancel: receiveCancel, + receiveTermination: receiveTermination, + receiveResult: receiveResult, + receiveRequest: receiveRequest + ) + } + + /// - Parameters: + /// - receiveTermination: Sent when the publisher either a completion event or is cancelled. + /// - receiveResult: Sent when the publisher send values, or an error. + /// - Please refer to the documentation for + /// `Publisher.self.handleEvents(receiveSubscription:receiveOutput:receiveCompletion:receiveCancel:receiveRequest:)` + /// for more information about the other parameters. + public func handleEvents( + receiveSubscription: ((Subscription) -> Void)? = nil, + receiveOutput: ((Output) -> Void)? = nil, + receiveCompletion: ((Subscribers.Completion) -> Void)? = nil, + receiveCancel: (() -> Void)? = nil, + receiveTermination: (() -> Void)? = nil, + receiveResult: @escaping (Result) -> Void, + receiveRequest: ((Subscribers.Demand) -> Void)? = nil + ) -> Publishers.HandleEvents { + self.extendedHandleEvents( + receiveSubscription: receiveSubscription, + receiveOutput: receiveOutput, + receiveCompletion: receiveCompletion, + receiveCancel: receiveCancel, + receiveTermination: receiveTermination, + receiveResult: receiveResult, + receiveRequest: receiveRequest + ) + } + + private func extendedHandleEvents( + receiveSubscription: ((Subscription) -> Void)? = nil, + receiveOutput: ((Output) -> Void)? = nil, + receiveCompletion: ((Subscribers.Completion) -> Void)? = nil, + receiveCancel: (() -> Void)? = nil, + receiveTermination: (() -> Void)? = nil, + receiveResult: ((Result) -> Void)?, + receiveRequest: ((Subscribers.Demand) -> Void)? = nil + ) -> Publishers.HandleEvents { + self.handleEvents( + receiveSubscription: receiveSubscription, + receiveOutput: { + receiveOutput?($0) + receiveResult?(.success($0)) + }, + receiveCompletion: { + receiveCompletion?($0) + if case .failure(let error) = $0 { + receiveResult?(.failure(error)) + } + receiveTermination?() + }, + receiveCancel: { + receiveCancel?() + receiveTermination?() + }, + receiveRequest: receiveRequest + ) + } +} + diff --git a/FueledUtils/Combine/PublisherExtensions.swift b/FueledUtils/Combine/PublisherExtensions.swift index cba448f9..28ac6686 100644 --- a/FueledUtils/Combine/PublisherExtensions.swift +++ b/FueledUtils/Combine/PublisherExtensions.swift @@ -19,16 +19,79 @@ extension Publisher { self.catch { _ in Empty() }.eraseToAnyPublisher() } + public func promoteOptional() -> AnyPublisher { + self.map { Optional.some($0) }.eraseToAnyPublisher() + } + public func sink() -> AnyCancellable { self.sink(receiveCompletion: { _ in }, receiveValue: { _ in }) } + + public func then(receiveResult: @escaping ((Result) -> Void)) -> AnyCancellable { + self.sink( + receiveCompletion: { completion in + if case .failure(let error) = completion { + receiveResult(.failure(error)) + } + }, + receiveValue: { value in + receiveResult(.success(value)) + } + ) + } + + public func sinkForLifetimeOf(_ object: Object) { + self.sink() + .store(in: &object.combineExtensions.cancellables) + } + + public func sinkForLifetimeOf(_ object: Object, receiveValue: @escaping ((Self.Output) -> Void)) where Failure == Never { + self.sink(receiveValue: receiveValue) + .store(in: &object.combineExtensions.cancellables) + } + + public func sinkForLifetimeOf(_ object: Object, receiveCompletion: @escaping ((Subscribers.Completion) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) { + self.sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue) + .store(in: &object.combineExtensions.cancellables) + } + + public func thenForLifetimeOf(_ object: Object, receiveResult: @escaping ((Result) -> Void)) { + self.then(receiveResult: receiveResult) + .store(in: &object.combineExtensions.cancellables) + } +} + +extension Publisher { + public func performDuringLifetimeOf(_ object: Object, action: @escaping (Object, Output) -> Void) { + self + .ignoreError() + .sinkForLifetimeOf(object) { [weak object] value in + guard let object = object else { + return + } + action(object, value) + } + } + + public func performDuringLifetimeOf(_ object: Object, action: @escaping (Object) -> (Output) -> Void) { + self.performDuringLifetimeOf(object) { object, output in + action(object)(output) + } + } } extension Publisher where Failure == Never { public func assign(to keyPath: ReferenceWritableKeyPath, withoutRetaining object: Object) -> AnyCancellable { - sink { [weak object] in + self.sink { [weak object] in + object?[keyPath: keyPath] = $0 + } + } + + public func assign(to keyPath: ReferenceWritableKeyPath, forLifetimeOf object: Object) -> Void { + self.sink { [weak object] in object?[keyPath: keyPath] = $0 } + .store(in: &object.combineExtensions.cancellables) } } diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index f273d675..7d7046a3 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; + F49963202537459200E2D4B5 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,6 +30,7 @@ F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSetSpec.swift; sourceTree = ""; }; + F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoalescingActionSpec.swift; sourceTree = ""; }; F7F10FE9C8384333882C2368 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; /* End PBXFileReference section */ @@ -99,6 +101,7 @@ F463C739241835DD000A0B29 /* Tests */ = { isa = PBXGroup; children = ( + F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */, F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */, @@ -238,6 +241,7 @@ files = ( F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */, F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */, + F49963202537459200E2D4B5 /* CoalescingActionSpec.swift in Sources */, F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tests/Tests/CoalescingActionSpec.swift b/Tests/Tests/CoalescingActionSpec.swift new file mode 100644 index 00000000..5b6ddef4 --- /dev/null +++ b/Tests/Tests/CoalescingActionSpec.swift @@ -0,0 +1,58 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine +import Quick +import Nimble +import FueledUtils + +class CoalescingActionSpec: QuickSpec { + override func spec() { + describe("CoalescingAction") { + describe("apply.dispose()") { + it("should dispose of all created signal producers") { + var subscriptionCounter = 0 + var cancelledCounter = 0 + let coalescingAction = CoalescingAction { + Just(2.0) + .delay(for: 1.0, scheduler: DispatchQueue.main) + .handleEvents( + receiveSubscription: { _ in + subscriptionCounter += 1 + }, + receiveCancel: { + cancelledCounter += 1 + } + ) + } + + expect(subscriptionCounter) == 0 + + let publishersCount = 5 + let cancellables = (0.. Date: Wed, 14 Oct 2020 14:04:07 -0400 Subject: [PATCH 53/89] feat(combine/uikit): add UIKit helpers for Combine --- FueledUtils.podspec | 7 ++ .../CombineUIKit/ControlProtocol+Tapped.swift | 68 ++++++++++++ FueledUtils/CombineUIKit/TapAction.swift | 100 ++++++++++++++++++ .../UIControl+ControlEventsPublisher.swift | 88 +++++++++++++++ .../CombineUIKit/UITextInput+Combine.swift | 39 +++++++ Tests/Podfile | 1 + Tests/Podfile.lock | 18 ++-- 7 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift create mode 100644 FueledUtils/CombineUIKit/TapAction.swift create mode 100644 FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift create mode 100644 FueledUtils/CombineUIKit/UITextInput+Combine.swift diff --git a/FueledUtils.podspec b/FueledUtils.podspec index eeb95045..5ae01320 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -65,6 +65,13 @@ Pod::Spec.new do |s| s.source_files = 'FueledUtils/CombineOperators/**/*.swift' end + s.subspec 'CombineUIKit' do |s| + s.dependency 'FueledUtils/Combine' + s.dependency 'FueledUtils/UIKit' + + s.source_files = 'FueledUtils/CombineUIKit/**/*.swift' + end + s.subspec 'SwiftUI' do |s| s.dependency 'FueledUtils/Core' s.dependency 'FueledUtils/Combine' diff --git a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift new file mode 100644 index 00000000..61d4b7db --- /dev/null +++ b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift @@ -0,0 +1,68 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine + +private var tapActionStorage: UInt8 = 0 +private var tapActionKey: UInt8 = 0 + +extension ControlProtocol { + /// + /// The action to be triggered when the button is tapped. + /// This mirrors the `pressed` property native in `ReactiveCocoa`, but uses a + /// protocol to represents the button rather than hardcode it to classes, + /// allowing for any `UIControl` to use this method. + /// + var tapped: TapAction? { + get { + self.tapActionStorage?.tapAction + } + set { + self.tapActionStorage = nil + + if let newValue = newValue { + let tapActionStorage = TapActionStorage(newValue) + newValue.$isEnabled.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &tapActionStorage.cancellables) + if let self = self as? ControlLoadingProtocol { + newValue.$isExecuting.sink { [weak self] in + self?.isLoading = $0 + } + .store(in: &tapActionStorage.cancellables) + } + self.removeTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) + self.addTarget(newValue, action: TapAction.selector, for: .primaryActionTriggered) + self.tapActionStorage = tapActionStorage + } + } + } + + private var tapActionStorage: TapActionStorage? { + get { + objc_getAssociatedObject(self, &tapActionKey) as? TapActionStorage + } + set { + objc_setAssociatedObject(self, &tapActionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + +private final class TapActionStorage { + let tapAction: TapAction + var cancellables = Set() + + init(_ tapAction: TapAction) { + self.tapAction = tapAction + } +} diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift new file mode 100644 index 00000000..0f437cbc --- /dev/null +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -0,0 +1,100 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine + +/// +/// `TapAction` wraps a `ActionProtocol` for use by any `ControlProtocol`. +final class TapAction: NSObject { + @objc static var selector: Selector { + #selector(userDidTapControl(_:)) + } + + @Published private(set) var isExecuting: Bool + @Published private(set) var isEnabled: Bool + + private var inputTransform: ((Control) -> Any)! + private var cancellables = Set() + private let action: AnyAction + + convenience init(_ action: Action) where Action.Input == Void { + self.init(action, input: ()) + } + + #if true + // Doing what's in the `else` branch below segfaults (more explanation below), + // so we use another method to do it... (Swift 5.3/Xcode 12.0 (12A7209)) + convenience init(_ action: Action, input: Action.Input) { + self.init(action: action) + self.initializeInput(input) + } + + convenience init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { + self.init(action: action) + self.initializeInputTransform(inputTransform) + } + + private init(action: Action) { + self.isEnabled = action.isEnabled + self.isExecuting = action.isExecuting + self.action = AnyAction(action) + super.init() + self.initializePublishers() + } + + private func initializeInput(_ input: Input) { + self.initializeInputTransform { _ in input } + } + + private func initializeInputTransform(_ inputTransform: @escaping (Control) -> Input) { + self.inputTransform = { inputTransform($0) } + } + + private func initializePublishers() { + self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) + .store(in: &self.cancellables) + } + #else + // FIXME: (Stéphane) To be retested for the next version of Swift (after 5.3) + // NOTE: The code is kept as it's how it be. + + // The issues here seems to be related to the closure, Swift doesn't seem to like passing them around directly + // from one initializer to another, hence the workaround above. + // It doesn't like initializer the publishers directly within the initializer, so we also have to create + // a method that does it for us. + // (I have a hunch it might be tied to the number of associated types in the `ActionProtocol` protocol) + convenience init(_ action: Action, input: Action.Input) { + self.init(action) { _ in input } + } + + init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { + self.isEnabled = action.isEnabled + self.isExecuting = action.isExecuting + self.inputTransform = { inputTransform($0) } + self.action = AnyAction(action) + super.init() + self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) + .store(in: &self.cancellables) + self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) + .store(in: &self.cancellables) + } + #endif + + @objc private func userDidTapControl(_ button: Any) { + self.action.apply(self.inputTransform(button as! Control)).sink() + .store(in: &self.cancellables) + } +} diff --git a/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift new file mode 100644 index 00000000..91e7959f --- /dev/null +++ b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift @@ -0,0 +1,88 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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 Combine +import UIKit + +private var publisherControlEventsProcessorsHolderKey: UInt8 = 0 + +extension ControlProtocol { + public func publisherForControlEvents(_ controlEvents: UIControl.Event) -> AnyPublisher { + let passthroughSubject = PassthroughSubject() + var cancellable: AnyCancellable! = self.publisherControlEventsProcessorsHolder.addProcessor( + for: controlEvents, + in: self, + passthroughSubject: passthroughSubject + ) + return passthroughSubject + .map { + $0 as! Self + } + .handleEvents( + receiveCancel: { + cancellable = nil + } + ) + .eraseToAnyPublisher() + } + + private var publisherControlEventsProcessorsHolder: PublisherControlEventsProcessorsHolder { + get { + objc_getAssociatedObject(self, &publisherControlEventsProcessorsHolderKey) as? PublisherControlEventsProcessorsHolder ?? { + let publisherControlEventsProcessorsHolder = PublisherControlEventsProcessorsHolder() + self.publisherControlEventsProcessorsHolder = publisherControlEventsProcessorsHolder + return publisherControlEventsProcessorsHolder + }() + } + set { + objc_setAssociatedObject(self, &publisherControlEventsProcessorsHolderKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + +private final class PublisherControlEventsProcessorsHolder { + private final class PublisherControlEventsProcessor: NSObject { + weak var passthroughSubject: PassthroughSubject! + + init(control: ControlProtocol, controlEvents: UIControl.Event, passthroughSubject: PassthroughSubject) { + self.passthroughSubject = passthroughSubject + super.init() + control.addTarget(self, action: #selector(PublisherControlEventsProcessor.handleControlEvents(_:)), for: controlEvents) + } + + @objc func handleControlEvents(_ sender: Any) { + self.passthroughSubject.send(sender) + } + } + + private var processors: [PublisherControlEventsProcessor] = [] + + init() { + } + + func addProcessor( + for controlEvents: UIControl.Event, + in control: ControlProtocol, + passthroughSubject: PassthroughSubject + ) -> AnyCancellable { + let processor = PublisherControlEventsProcessor( + control: control, + controlEvents: controlEvents, + passthroughSubject: passthroughSubject + ) + return AnyCancellable { + self.processors.removeAll { $0 === processor } + } + } +} diff --git a/FueledUtils/CombineUIKit/UITextInput+Combine.swift b/FueledUtils/CombineUIKit/UITextInput+Combine.swift new file mode 100644 index 00000000..11e81342 --- /dev/null +++ b/FueledUtils/CombineUIKit/UITextInput+Combine.swift @@ -0,0 +1,39 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine +import UIKit + +extension CombineExtensions where Base: UITextInput, Base: ControlProtocol { + var textValues: AnyPublisher { + self.textPublisherForControlEvents([.editingDidEnd, .editingDidEndOnExit]) + } + + var continuousTextValues: AnyPublisher { + self.textPublisherForControlEvents(.allEditingEvents) + } + + private func textPublisherForControlEvents(_ controlEvents: UIControl.Event) -> AnyPublisher { + self.base.publisherForControlEvents(controlEvents) + .map { textInput in + textInput.textRange( + from: textInput.beginningOfDocument, + to: textInput.endOfDocument + ) + .flatMap { textInput.text(in: $0) } + ?? "" + } + .eraseToAnyPublisher() + } +} diff --git a/Tests/Podfile b/Tests/Podfile index abd60906..d02cad75 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -13,5 +13,6 @@ abstract_target 'Common' do pod 'FueledUtils/ReactiveSwiftUIKit', path: '../' pod 'FueledUtils/SwiftUI', path: '../' pod 'FueledUtils/CombineOperators', path: '../' + pod 'FueledUtils/CombineUIKit', path: '../' end end diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 9bfa0be7..a302f39a 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -3,6 +3,9 @@ PODS: - FueledUtils/ReactiveCommon - FueledUtils/CombineOperators (3.0-alpha1): - FueledUtils/Combine + - FueledUtils/CombineUIKit (3.0-alpha1): + - FueledUtils/Combine + - FueledUtils/UIKit - FueledUtils/Core (3.0-alpha1) - FueledUtils/ReactiveCombineBridge (3.0-alpha1): - FueledUtils/Combine @@ -23,12 +26,13 @@ PODS: - FueledUtils/Core - Nimble (8.0.5) - Quick (2.2.0) - - ReactiveCocoa (10.2.0): + - ReactiveCocoa (10.3.0): - ReactiveSwift (~> 6.2) - - ReactiveSwift (6.2.1) + - ReactiveSwift (6.4.0) DEPENDENCIES: - FueledUtils/CombineOperators (from `../`) + - FueledUtils/CombineUIKit (from `../`) - FueledUtils/ReactiveCombineBridge (from `../`) - FueledUtils/ReactiveSwift (from `../`) - FueledUtils/ReactiveSwiftUIKit (from `../`) @@ -48,12 +52,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: ac740e5dae55bc10029aa3cbcd8b533298622d7a + FueledUtils: 988e17c4f4aef059750c2e501368c24ca8172d25 Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e - ReactiveCocoa: a123c42f449c552460a4ee217dd49c76a17c8204 - ReactiveSwift: 07ddf579f4eb3ee3bd656214f0461aaf2c0fd639 + ReactiveCocoa: 083ae559e6f588ce519cab412ea119b431c26a24 + ReactiveSwift: 7555791a608c0679563a3f72546f971b2a06de98 -PODFILE CHECKSUM: aeaba40223e95702eeaff4c66b27d8c72fcee5f5 +PODFILE CHECKSUM: 32300607a3a6ea0867f9cc805c81c934b352b46c -COCOAPODS: 1.9.3 +COCOAPODS: 1.10.0.rc.1 From 44e6c2ebe1524dc715a5e89c6b3d7281555f5ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 16 Oct 2020 15:23:46 -0400 Subject: [PATCH 54/89] chore(changelog): add changelog entry --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee8044e..6d71b1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,32 +2,46 @@ ##### New Features/Enhancements -- Add an optional `insets` parameter to `addAndFitSubview()` +- Add `ActionProtocol` +- Add `AnyAction`, allowing to type-erase any actions represented by a `ActionProtocol` +- Add `CoalescingAction` & `OverridingAction` +- Add a `CombineExtensions` & `CombineExtensionsProvider` protocol, replicating the `.reactive` of ReactiveSwift (so as not to pollute too much the global namespace) +- All `NSObject` now can have a `cancellables` object attached to them via `.combineExtensions.cancellables`. It's created lazily, so there's impact if not used. +- Extend `Publisher.handleEvents` to add two new hooks: + - `receiveTermination`: When either a completion or a cancellation is received + - `receiveResult`: Takes a `Result`, and called when either values are received or an error is received +- Add `Publisher.promoteOptional()` +- Add `then(receiveResult:)`, which takes a closure with a `Result`, allowing to handle values & error in the same place +- Add `sinkForLifetimeOf(_:)` methods family, allowing to sink on a publisher and link to the lifetime of a given `CombineExtensionsProvider & AnyObject`. The goal of this is to avoid having to write the classic boilerplate code in Combine handling with having to create `cancellables` for every single object (this used the `cancellables` extension mentioned above). +- Add `performDuringLifetimeOf(_:action:)`, allowing to link an action with the lifetime of an object. This act as an equivalent for `makeBindingTarget` from `ReactiveSwift` when calling functions or assigning multiple variables. +- Add `assign(to:forLifetimeOf:)`, allowing to assign the output of the producer to a keyPath, keeping it alive until the specified object is deallocated. +- Add `TapAction` and `.combineExtensions.tapped`, allowing to link an `Action` to a button, without having to do the bindings manually (similar to `UIButton.reactive.pressed` in ReactiveCocoa) +- Add `.publisherForControlEvent(_:)`, to get a publisher that triggers on any control events. +- Add `(UITextField/UITextView).(textValues|continuousTextValues)`, which are equivalent to same thing as for ReactiveCocoa. [Stéphane Copin](https://github.com/stephanecopin) - [#53](https://github.com/Fueled/ios-utilities/pull/53) + [#54](https://github.com/Fueled/ios-utilities/pull/54) +- Add an optional `insets` parameter to `addAndFitSubview()` - Make `removeArrangedSubviews()`'s `removeFromHierachy` parameter default to `true` - [Stéphane Copin](https://github.com/stephanecopin) - [#53](https://github.com/Fueled/ios-utilities/pull/53) - - Add `tapped` helper to link any `ReactiveActionProtocol` to any `UIControl` - [Stéphane Copin](https://github.com/stephanecopin) - [#53](https://github.com/Fueled/ios-utilities/pull/53) - - Add `AnyIdentifiable` & `AnyAction` for type-erased `Identifiable` & `ReactiveActionProtocol` respectively - [Stéphane Copin](https://github.com/stephanecopin) - [#53](https://github.com/Fueled/ios-utilities/pull/53) - - Add `OverridingAction`, a new `Action` that if executed when already executing, will cancel the previous producer and start a new one - [Stéphane Copin](https://github.com/stephanecopin) - [#53](https://github.com/Fueled/ios-utilities/pull/53) - - Make `OrderedSet` conform to `SetAlgebra` [Stéphane Copin](https://github.com/stephanecopin) [#53](https://github.com/Fueled/ios-utilities/pull/53) ##### Bug Fixes +- Fix a bug in `Action` where a cancellation would be ignored and not set `isExecuting` to `false` + [Stéphane Copin](https://github.com/stephanecopin) + [#54](https://github.com/Fueled/ios-utilities/pull/54) + - Fix an internal state corruption issue in `OrderedSet` [Stéphane Copin](https://github.com/stephanecopin) [#53](https://github.com/Fueled/ios-utilities/pull/53) + +##### Breaking changes + +- The original `TapAction`, `OverridingAction` and `AnyAction` were all prefixed with `Reactive`. + [Stéphane Copin](https://github.com/stephanecopin) + [#54](https://github.com/Fueled/ios-utilities/pull/54) From 3d7e598cf2c626d82fb89444c25bbbd325208b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 14 Oct 2020 16:26:54 -0400 Subject: [PATCH 55/89] test(*): add unit tests --- Tests/FueledUtils.xcodeproj/project.pbxproj | 10 ++- Tests/Tests/CoalescingActionSpec.swift | 2 +- Tests/Tests/OverridingActionSpec.swift | 64 +++++++++++++++++++ .../Tests/ReactiveOverridingActionSpec.swift | 62 ++++++++++++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 Tests/Tests/OverridingActionSpec.swift create mode 100644 Tests/Tests/ReactiveOverridingActionSpec.swift diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 7d7046a3..a41f19ce 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */; }; + F429742A25378348004BFA85 /* ReactiveOverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */; }; + F429742E25378425004BFA85 /* OverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742D25378425004BFA85 /* OverridingActionSpec.swift */; }; F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; @@ -25,6 +27,8 @@ D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveOverridingActionSpec.swift; sourceTree = ""; }; + F429742D25378425004BFA85 /* OverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridingActionSpec.swift; sourceTree = ""; }; F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -102,9 +106,11 @@ isa = PBXGroup; children = ( F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */, + F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */, + F429742D25378425004BFA85 /* OverridingActionSpec.swift */, F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, - F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */, + F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */, ); path = Tests; sourceTree = ""; @@ -240,9 +246,11 @@ buildActionMask = 2147483647; files = ( F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */, + F429742A25378348004BFA85 /* ReactiveOverridingActionSpec.swift in Sources */, F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */, F49963202537459200E2D4B5 /* CoalescingActionSpec.swift in Sources */, F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, + F429742E25378425004BFA85 /* OverridingActionSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/Tests/CoalescingActionSpec.swift b/Tests/Tests/CoalescingActionSpec.swift index 5b6ddef4..2797a078 100644 --- a/Tests/Tests/CoalescingActionSpec.swift +++ b/Tests/Tests/CoalescingActionSpec.swift @@ -13,9 +13,9 @@ // limitations under the License. import Combine +import FueledUtils import Quick import Nimble -import FueledUtils class CoalescingActionSpec: QuickSpec { override func spec() { diff --git a/Tests/Tests/OverridingActionSpec.swift b/Tests/Tests/OverridingActionSpec.swift new file mode 100644 index 00000000..74ae12ed --- /dev/null +++ b/Tests/Tests/OverridingActionSpec.swift @@ -0,0 +1,64 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine +import FueledUtils +import Quick +import Nimble + +class OverridingActionSpec: QuickSpec { + private var cancellables: [AnyCancellable]! + + override func spec() { + describe("OverridingAction") { + beforeEach { + self.cancellables = [] + } + describe("apply.sink()") { + it("should cancel the previous work") { + var subscriptionCounter = 0 + var cancelledCounter = 0 + var interruptedCounter = 0 + let coalescingAction = OverridingAction { + Just(2.0) + .delay(for: 1.0, scheduler: DispatchQueue.main) + .handleEvents( + receiveSubscription: { _ in + subscriptionCounter += 1 + }, + receiveCancel: { + cancelledCounter += 1 + }, + receiveTermination: { + interruptedCounter += 1 + } + ) + } + + expect(subscriptionCounter) == 0 + + let publishersCount = 5 + self.cancellables = (0.. Date: Thu, 15 Oct 2020 16:37:45 -0400 Subject: [PATCH 56/89] test(combine/latest-many): add test for CombineLatestMany --- Tests/FueledUtils.xcodeproj/project.pbxproj | 28 +-- Tests/Tests/CombineLatestManySpec.swift | 192 ++++++++++++++++++++ 2 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 Tests/Tests/CombineLatestManySpec.swift diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index a41f19ce..fe859415 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -8,12 +8,13 @@ /* Begin PBXBuildFile section */ EE9B3A498587AC2D3461D310 /* Pods_Common_FueledUtilsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */; }; - F429742A25378348004BFA85 /* ReactiveOverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */; }; - F429742E25378425004BFA85 /* OverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742D25378425004BFA85 /* OverridingActionSpec.swift */; }; - F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; - F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; - F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; - F49963202537459200E2D4B5 /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */; }; + F453AA7B2538DE55008F045B /* CombineLatestManySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */; }; + F453AA7F2538E05E008F045B /* ReactiveSwiftExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */; }; + F453AA802538E05E008F045B /* OrderedSetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46D2DA5252E4E4A00B6987A /* OrderedSetSpec.swift */; }; + F453AA812538E05E008F045B /* CoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F499631F2537459200E2D4B5 /* CoalescingActionSpec.swift */; }; + F453AA822538E05E008F045B /* OverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742D25378425004BFA85 /* OverridingActionSpec.swift */; }; + F453AA832538E05E008F045B /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; + F453AA842538E05E008F045B /* ReactiveOverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,6 +30,7 @@ E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveOverridingActionSpec.swift; sourceTree = ""; }; F429742D25378425004BFA85 /* OverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridingActionSpec.swift; sourceTree = ""; }; + F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineLatestManySpec.swift; sourceTree = ""; }; F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -111,6 +113,7 @@ F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */, F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */, + F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */, ); path = Tests; sourceTree = ""; @@ -245,12 +248,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F46D2DA6252E4E4A00B6987A /* OrderedSetSpec.swift in Sources */, - F429742A25378348004BFA85 /* ReactiveOverridingActionSpec.swift in Sources */, - F463C73C241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift in Sources */, - F49963202537459200E2D4B5 /* CoalescingActionSpec.swift in Sources */, - F463C73D241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift in Sources */, - F429742E25378425004BFA85 /* OverridingActionSpec.swift in Sources */, + F453AA7B2538DE55008F045B /* CombineLatestManySpec.swift in Sources */, + F453AA802538E05E008F045B /* OrderedSetSpec.swift in Sources */, + F453AA822538E05E008F045B /* OverridingActionSpec.swift in Sources */, + F453AA832538E05E008F045B /* ReactiveCoalescingActionSpec.swift in Sources */, + F453AA812538E05E008F045B /* CoalescingActionSpec.swift in Sources */, + F453AA7F2538E05E008F045B /* ReactiveSwiftExtensionsSpec.swift in Sources */, + F453AA842538E05E008F045B /* ReactiveOverridingActionSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/Tests/CombineLatestManySpec.swift b/Tests/Tests/CombineLatestManySpec.swift new file mode 100644 index 00000000..20ce9c2d --- /dev/null +++ b/Tests/Tests/CombineLatestManySpec.swift @@ -0,0 +1,192 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Combine +import FueledUtils +import Quick +import Nimble + +class CombineLatestManySpec: QuickSpec { + private var cancellables: [AnyCancellable]! + + override func spec() { + describe("Publishers.CombineLatestMany") { + beforeEach { + self.cancellables = [] + } + describe("with zero elements") { + it("should complete instantaneously with an empty array and no errors") { + var completionCount = 0 + var valueCount = 0 + Publishers.CombineLatestMany<[Just]>([]).sink( + receiveCompletion: { completion in + let isFinished: Bool + switch completion { + case .failure: + isFinished = false + case .finished: + isFinished = true + } + expect(isFinished) == true + completionCount += 1 + }, + receiveValue: { values in + expect(values).to(haveCount(0)) + valueCount += 1 + } + ) + .store(in: &self.cancellables) + + expect(completionCount) == 1 + expect(valueCount) == 1 + } + } + describe("with two elements") { + it("should match the native CombineLatest behavior") { + var completionCount = 0 + var valueCount = 0 + var nativeValues: [Int] = [] + var manyValues: [Int] = [] + + func publisher(_ value: Int) -> AnyPublisher { + Just(value).delay(for: 0.3, scheduler: DispatchQueue.main) + .eraseToAnyPublisher() + } + + Publishers.CombineLatest( + publisher(1).append(publisher(3)), + publisher(2) + ) + .sink( + receiveCompletion: { completion in + let isFinished: Bool + switch completion { + case .failure: + isFinished = false + case .finished: + isFinished = true + } + expect(isFinished) == true + completionCount += 1 + }, + receiveValue: { value1, value2 in + nativeValues = [value1, value2] + valueCount += 1 + } + ) + .store(in: &self.cancellables) + + Publishers.CombineLatestMany( + [ + publisher(1).append(publisher(3)).eraseToAnyPublisher(), + publisher(2).eraseToAnyPublisher() + ] + ) + .sink( + receiveCompletion: { completion in + let isFinished: Bool + switch completion { + case .failure: + isFinished = false + case .finished: + isFinished = true + } + expect(isFinished) == true + completionCount += 1 + }, + receiveValue: { values in + manyValues = values + valueCount += 1 + } + ) + .store(in: &self.cancellables) + + expect(completionCount).toEventually(equal(2)) + expect(valueCount).toEventually(equal(4)) + + expect(nativeValues).toEventually(equal([3, 2])) + expect(manyValues).toEventually(equal([3, 2])) + } + } + describe("with two publishers") { + it("should correctly interrupt the publishers when interrupted") { + var subscriptionCount = 0 + var cancelCount = 0 + var nativeValueCount = 0 + var nativeValues: [Int] = [] + var manyValueCount = 0 + var manyValues: [Int] = [] + + func publisher(_ value: Int) -> AnyPublisher { + Just(1).delay(for: 0.5, scheduler: DispatchQueue.main) + .handleEvents( + receiveSubscription: { _ in + subscriptionCount += 1 + }, + receiveCancel: { + cancelCount += 1 + } + ) + .eraseToAnyPublisher() + } + + var cancellable = Publishers.CombineLatest( + publisher(1), + publisher(2) + ) + .handleEvents( + receiveCancel: { + cancelCount += 1 + } + ) + .sink( + receiveValue: { value1, value2 in + nativeValues = [value1, value2] + nativeValueCount += 1 + } + ) + + cancellable = Publishers.CombineLatestMany( + [ + publisher(1), + publisher(2) + ] + ) + .handleEvents( + receiveCancel: { + cancelCount += 1 + } + ) + .sink( + receiveValue: { values in + manyValues = values + manyValueCount += 1 + } + ) + + cancellable.cancel() + + expect(subscriptionCount) == 4 + expect(cancelCount).toEventually(equal(6)) + + expect(nativeValueCount).toEventually(equal(0)) + expect(nativeValues).toEventually(haveCount(0)) + + expect(manyValueCount).toEventually(equal(0)) + expect(manyValues).toEventually(haveCount(0)) + } + } + } + } +} From fce9b52e631bca04c2d7f4185664b199b985dff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 15 Oct 2020 16:38:10 -0400 Subject: [PATCH 57/89] fix(combine/latest-many): fix CombineLatestMany implementation which wouldn't interrupt properly its publishers --- .../Combine/PublishersExtensions.swift | 142 ++++++++++++------ 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index e16a22da..8345e703 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -27,57 +27,115 @@ extension Publishers { } public func receive(subscriber: Subscriber) where PublisherCollection.Element.Failure == Subscriber.Failure, Subscriber.Input == Output { - if self.publishers.isEmpty { - _ = subscriber.receive([]) - subscriber.receive(completion: .finished) - return + let subscription = CombineLatestManySubscription(subscriber: subscriber, publishers: self.publishers) + subscription.startReceiving() + } + } +} + +private final class CombineLatestManySubscription< + Subscriber: Combine.Subscriber, + PublisherCollection: Swift.Collection +>: Subscription where + PublisherCollection.Element: Combine.Publisher, + PublisherCollection.Element.Failure == Subscriber.Failure, + Subscriber.Input == [PublisherCollection.Element.Output] +{ + private var currentDemand: Subscribers.Demand! + private var pendingValuesBuffer: [Subscriber.Input] = [] + private let subscriber: Subscriber + private let publishers: PublisherCollection + private var cancellables: [AnyCancellable] = [] + + init(subscriber: Subscriber, publishers: PublisherCollection) { + self.subscriber = subscriber + self.publishers = publishers + } + + func startReceiving() { + self.subscriber.receive(subscription: self) + } + + func request(_ demand: Subscribers.Demand) { + if self.publishers.isEmpty { + self.currentDemand = demand + self.sendValueIfPossible([]) + self.subscriber.receive(completion: .finished) + return + } + + if let currentDemand = self.currentDemand { + self.currentDemand += demand + while let firstValue = self.pendingValuesBuffer.first, self.currentDemand > 0 { + self.pendingValuesBuffer.removeFirst() + self.sendValueIfPossible(firstValue) } + return + } - let publishers = Array(self.publishers) - let cancellables = AtomicValue( - [ - ( - cancellable: AnyCancellable?, - latestValue: PublisherCollection.Element.Output?, - hasCompleted: Bool - ), - ](repeating: (nil, nil, false), count: self.publishers.count) - ) - publishers.enumerated().forEach { index, publisher in - let cancellable = publisher.sink( - receiveCompletion: { completion in - cancellables.modify { - switch completion { - case .failure(let error): - subscriber.receive(completion: .failure(error)) - for i in $0.indices { - $0[i].cancellable = nil - } - case .finished: - $0[index].cancellable = nil - $0[index].hasCompleted = true - if $0.allSatisfy({ $0.hasCompleted }) { - subscriber.receive(completion: .finished) - } + self.currentDemand = demand + + let publishers = Array(self.publishers) + let cancellables = AtomicValue( + [ + ( + cancellable: AnyCancellable?, + latestValue: PublisherCollection.Element.Output?, + hasCompleted: Bool + ), + ](repeating: (nil, nil, false), count: self.publishers.count) + ) + publishers.enumerated().forEach { index, publisher in + let cancellable = publisher.sink( + receiveCompletion: { completion in + cancellables.modify { + switch completion { + case .failure(let error): + self.subscriber.receive(completion: .failure(error)) + for i in $0.indices { + $0[i].cancellable = nil } - } - }, - receiveValue: { value in - cancellables.modify { - $0[index].latestValue = value - let allLatestValues = $0.compactMap { $0.latestValue } - if allLatestValues.count == publishers.count { - _ = subscriber.receive(allLatestValues) + case .finished: + $0[index].cancellable = nil + $0[index].hasCompleted = true + if $0.allSatisfy({ $0.hasCompleted }) { + self.subscriber.receive(completion: .finished) } } } - ) - cancellables.modify { - if !$0[index].hasCompleted { - $0[index].cancellable = cancellable + }, + receiveValue: { value in + cancellables.modify { + $0[index].latestValue = value + let allLatestValues = $0.compactMap { $0.latestValue } + if allLatestValues.count == publishers.count { + self.sendValueIfPossible(allLatestValues) + } } } + ) + cancellables.modify { + if !$0[index].hasCompleted { + $0[index].cancellable = cancellable + } + self.cancellables = $0.compactMap { $0.cancellable } } } } + + func cancel() { + self.currentDemand = .none + self.pendingValuesBuffer = [] + self.cancellables.forEach { $0.cancel() } + } + + private func sendValueIfPossible(_ value: Subscriber.Input) { + if self.currentDemand == 0 { + self.pendingValuesBuffer.append(value) + return + } + + self.subscriber.receive(value) + self.currentDemand -= 1 + } } From 52e1805587923267cca05857d6daa7c2d2006b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 15 Oct 2020 16:49:33 -0400 Subject: [PATCH 58/89] refactor(combine/latest-many): make CombineLatestMany thread-safe --- .../Combine/PublishersExtensions.swift | 85 ++++++++++++------- FueledUtils/Core/Atomic.swift | 13 ++- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index 8345e703..bc7c53d9 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -41,11 +41,21 @@ private final class CombineLatestManySubscription< PublisherCollection.Element.Failure == Subscriber.Failure, Subscriber.Input == [PublisherCollection.Element.Output] { - private var currentDemand: Subscribers.Demand! - private var pendingValuesBuffer: [Subscriber.Input] = [] + @Atomic private var demandsState = DemandsState() private let subscriber: Subscriber private let publishers: PublisherCollection - private var cancellables: [AnyCancellable] = [] + + private struct DemandsState { + var currentDemand: Subscribers.Demand! + var pendingValuesBuffer: [Subscriber.Input] = [] + var cancellables: [AnyCancellable] = [] + + mutating func cancel() { + self.currentDemand = .none + self.pendingValuesBuffer = [] + self.cancellables.forEach { $0.cancel() } + } + } init(subscriber: Subscriber, publishers: PublisherCollection) { self.subscriber = subscriber @@ -58,22 +68,45 @@ private final class CombineLatestManySubscription< func request(_ demand: Subscribers.Demand) { if self.publishers.isEmpty { - self.currentDemand = demand - self.sendValueIfPossible([]) + if demand > 0 { + self.subscriber.receive([]) + } self.subscriber.receive(completion: .finished) return } - if let currentDemand = self.currentDemand { - self.currentDemand += demand - while let firstValue = self.pendingValuesBuffer.first, self.currentDemand > 0 { - self.pendingValuesBuffer.removeFirst() - self.sendValueIfPossible(firstValue) + func sendValueIfPossible(_ value: Subscriber.Input, demandsState: inout DemandsState) { + if demandsState.currentDemand == nil { + // Cancelled + return } - return + + if demandsState.currentDemand == 0 { + demandsState.pendingValuesBuffer.append(value) + return + } + + self.subscriber.receive(value) + demandsState.currentDemand -= 1 } - self.currentDemand = demand + let shouldReturn = self.$demandsState.modify { demandsState -> Bool in + if let currentDemand = demandsState.currentDemand { + demandsState.currentDemand += demand + while let firstValue = demandsState.pendingValuesBuffer.first, demandsState.currentDemand > 0 { + demandsState.pendingValuesBuffer.removeFirst() + sendValueIfPossible(firstValue, demandsState: &demandsState) + } + return true + } + + demandsState.currentDemand = demand + return false + } + + if shouldReturn { + return + } let publishers = Array(self.publishers) let cancellables = AtomicValue( @@ -109,33 +142,25 @@ private final class CombineLatestManySubscription< $0[index].latestValue = value let allLatestValues = $0.compactMap { $0.latestValue } if allLatestValues.count == publishers.count { - self.sendValueIfPossible(allLatestValues) + self.$demandsState.modify { + sendValueIfPossible(allLatestValues, demandsState: &$0) + } } } } ) - cancellables.modify { - if !$0[index].hasCompleted { - $0[index].cancellable = cancellable + cancellables.modify { cancellables in + if !cancellables[index].hasCompleted { + cancellables[index].cancellable = cancellable + } + self.$demandsState.modify { + $0.cancellables = cancellables.compactMap { $0.cancellable } } - self.cancellables = $0.compactMap { $0.cancellable } } } } func cancel() { - self.currentDemand = .none - self.pendingValuesBuffer = [] - self.cancellables.forEach { $0.cancel() } - } - - private func sendValueIfPossible(_ value: Subscriber.Input) { - if self.currentDemand == 0 { - self.pendingValuesBuffer.append(value) - return - } - - self.subscriber.receive(value) - self.currentDemand -= 1 + self.$demandsState.modify { $0.cancel() } } } diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift index 0fb9070a..f3021510 100644 --- a/FueledUtils/Core/Atomic.swift +++ b/FueledUtils/Core/Atomic.swift @@ -61,11 +61,20 @@ public struct Atomic { self.atomicValue.value } - public mutating func modify(_ modify: (inout Value) -> Void) { + public var projectedValue: Atomic { + get { + self + } + set { + self = newValue + } + } + + public mutating func modify(_ modify: (inout Value) -> Return) -> Return { self.atomicValue.modify(modify) } - public mutating func withValue(_ getter: (Value) -> Void) { + public mutating func withValue(_ getter: (Value) -> Return) -> Return { self.atomicValue.withValue(getter) } From 4bbfbbe87f16dc87111582045c586f8c4afacfdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 15 Oct 2020 17:12:36 -0400 Subject: [PATCH 59/89] test(combine/sink-lifetime): add test for sinkForLifetimeOf() --- Tests/FueledUtils.xcodeproj/project.pbxproj | 4 ++ Tests/Tests/SinkForLifetimeSpec.swift | 51 +++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 Tests/Tests/SinkForLifetimeSpec.swift diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index fe859415..113108e0 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ F453AA822538E05E008F045B /* OverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742D25378425004BFA85 /* OverridingActionSpec.swift */; }; F453AA832538E05E008F045B /* ReactiveCoalescingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */; }; F453AA842538E05E008F045B /* ReactiveOverridingActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */; }; + F453AA892538EF16008F045B /* SinkForLifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F453AA882538EF16008F045B /* SinkForLifetimeSpec.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,6 +32,7 @@ F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveOverridingActionSpec.swift; sourceTree = ""; }; F429742D25378425004BFA85 /* OverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridingActionSpec.swift; sourceTree = ""; }; F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineLatestManySpec.swift; sourceTree = ""; }; + F453AA882538EF16008F045B /* SinkForLifetimeSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinkForLifetimeSpec.swift; sourceTree = ""; }; F463C73A241835DD000A0B29 /* ReactiveCoalescingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCoalescingActionSpec.swift; sourceTree = ""; }; F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveSwiftExtensionsSpec.swift; sourceTree = ""; }; F463C73F241835EA000A0B29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -114,6 +116,7 @@ F463C73B241835DD000A0B29 /* ReactiveSwiftExtensionsSpec.swift */, F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */, F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */, + F453AA882538EF16008F045B /* SinkForLifetimeSpec.swift */, ); path = Tests; sourceTree = ""; @@ -250,6 +253,7 @@ files = ( F453AA7B2538DE55008F045B /* CombineLatestManySpec.swift in Sources */, F453AA802538E05E008F045B /* OrderedSetSpec.swift in Sources */, + F453AA892538EF16008F045B /* SinkForLifetimeSpec.swift in Sources */, F453AA822538E05E008F045B /* OverridingActionSpec.swift in Sources */, F453AA832538E05E008F045B /* ReactiveCoalescingActionSpec.swift in Sources */, F453AA812538E05E008F045B /* CoalescingActionSpec.swift in Sources */, diff --git a/Tests/Tests/SinkForLifetimeSpec.swift b/Tests/Tests/SinkForLifetimeSpec.swift new file mode 100644 index 00000000..56291f3b --- /dev/null +++ b/Tests/Tests/SinkForLifetimeSpec.swift @@ -0,0 +1,51 @@ +// Copyright © 2020 Fueled Digital Media, LLC +// +// 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 Quick +import Nimble +import FueledUtils +import ReactiveSwift +import Combine + +class SinkForLifetimeSpec: QuickSpec { + override func spec() { + describe("sinkForLifeTimeOf()") { + it("should cancel itself automatically when the object becomes out of scope") { + var object: NSObject! + var valueCount = 0 + var cancelCount = 0 + do { + object = NSObject() + Timer.publish(every: 0.42, on: .current, in: .common) + .autoconnect() + .handleEvents( + receiveCancel: { + cancelCount += 1 + } + ) + .sinkForLifetimeOf(object) { _ in + valueCount += 1 + } + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + object = nil + } + + expect(valueCount).toEventually(equal(2)) + expect(cancelCount).toEventually(equal(1), timeout: 2.0) + } + } + } +} From a24d5ada455de518ddccfb2fe10a381b07eec9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 16 Oct 2020 15:25:08 -0400 Subject: [PATCH 60/89] chore(changelog): add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d71b1a1..81c5e481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,10 @@ ##### Bug Fixes +- Fix a bug in `CombineLatestMany` where cancelling the resulting publisher would not cancel the array of publishers themselves. + [Stéphane Copin](https://github.com/stephanecopin) + [#55](https://github.com/Fueled/ios-utilities/pull/55) + - Fix a bug in `Action` where a cancellation would be ignored and not set `isExecuting` to `false` [Stéphane Copin](https://github.com/stephanecopin) [#54](https://github.com/Fueled/ios-utilities/pull/54) From 6d9a44859bda2905494ee2874c65b1380479d5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 8 Dec 2020 17:29:29 -0500 Subject: [PATCH 61/89] fix(tap-action): remove initializers of TapAction due to segfault --- FueledUtils/CombineUIKit/TapAction.swift | 57 ++++-------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 0f437cbc..29ebb4d2 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -21,61 +21,20 @@ final class TapAction: NSObject { #selector(userDidTapControl(_:)) } - @Published private(set) var isExecuting: Bool - @Published private(set) var isEnabled: Bool + @Published private(set) var isExecuting: Bool = false + @Published private(set) var isEnabled: Bool = false - private var inputTransform: ((Control) -> Any)! - private var cancellables = Set() + // FIXME: (Stéphane) To be retested for the next version of Swift (after 5.3) + // Any initializers below create a segfault when compiling with optimizations. + #if false + private let inputTransform: ((Control) -> Any) private let action: AnyAction + private var cancellables = Set() convenience init(_ action: Action) where Action.Input == Void { self.init(action, input: ()) } - #if true - // Doing what's in the `else` branch below segfaults (more explanation below), - // so we use another method to do it... (Swift 5.3/Xcode 12.0 (12A7209)) - convenience init(_ action: Action, input: Action.Input) { - self.init(action: action) - self.initializeInput(input) - } - - convenience init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { - self.init(action: action) - self.initializeInputTransform(inputTransform) - } - - private init(action: Action) { - self.isEnabled = action.isEnabled - self.isExecuting = action.isExecuting - self.action = AnyAction(action) - super.init() - self.initializePublishers() - } - - private func initializeInput(_ input: Input) { - self.initializeInputTransform { _ in input } - } - - private func initializeInputTransform(_ inputTransform: @escaping (Control) -> Input) { - self.inputTransform = { inputTransform($0) } - } - - private func initializePublishers() { - self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) - .store(in: &self.cancellables) - self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) - .store(in: &self.cancellables) - } - #else - // FIXME: (Stéphane) To be retested for the next version of Swift (after 5.3) - // NOTE: The code is kept as it's how it be. - - // The issues here seems to be related to the closure, Swift doesn't seem to like passing them around directly - // from one initializer to another, hence the workaround above. - // It doesn't like initializer the publishers directly within the initializer, so we also have to create - // a method that does it for us. - // (I have a hunch it might be tied to the number of associated types in the `ActionProtocol` protocol) convenience init(_ action: Action, input: Action.Input) { self.init(action) { _ in input } } @@ -94,7 +53,9 @@ final class TapAction: NSObject { #endif @objc private func userDidTapControl(_ button: Any) { + #if false self.action.apply(self.inputTransform(button as! Control)).sink() .store(in: &self.cancellables) + #endif } } From 218393e2ddec92473cd29a90c01b98792a884e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 22 Oct 2020 15:41:03 -0400 Subject: [PATCH 62/89] chore(podspec): update PodSpec & code to allow for iOS 12/SwiftUI --- FueledUtils.podspec | 20 ++++++++++--------- FueledUtils/Combine/Action.swift | 3 +++ FueledUtils/Combine/ActionError.swift | 3 +++ FueledUtils/Combine/ActionProtocol.swift | 3 +++ FueledUtils/Combine/AnyAction.swift | 1 + .../Combine/AnyCurrentValuePublisher.swift | 4 ++++ FueledUtils/Combine/Binding+KeyPath.swift | 1 + FueledUtils/Combine/CoalescingAction.swift | 1 + .../CombineExtensions+Cancellables.swift | 1 + FueledUtils/Combine/CombineExtensions.swift | 3 +++ .../Combine/NSObject+CombineExtensions.swift | 1 + .../Combine/ObservableObjectExtensions.swift | 3 +++ FueledUtils/Combine/OverridingAction.swift | 1 + .../Combine/Published+PublisherInit.swift | 1 + .../Publisher+AdditionalHandleEvents.swift | 1 + .../Combine/Publisher+CombinePrevious.swift | 1 + .../Combine/Publisher+IgnoreLoading.swift | 2 ++ .../Combine/Publisher+IgnoreRepeats.swift | 2 ++ FueledUtils/Combine/PublisherExtensions.swift | 4 ++++ .../Combine/PublishersExtensions.swift | 2 ++ FueledUtils/Combine/Subject+SendResult.swift | 1 + .../Subscriber+EraseToAnySubscriber.swift | 1 + .../CombineOperators/Combine+Operators.swift | 12 +++++++++++ .../CombineOperators+Optional.swift | 6 ++++++ .../CombineUIKit/ControlProtocol+Tapped.swift | 4 ++++ FueledUtils/CombineUIKit/TapAction.swift | 3 +++ .../UIControl+ControlEventsPublisher.swift | 4 ++++ .../CombineUIKit/UITextInput+Combine.swift | 3 +++ FueledUtils/Core/AnyIdentifiable.swift | 2 +- FueledUtils/Core/Lock.swift | 4 ++-- .../Combine+ReactiveSwift.swift | 4 ++++ .../ReactiveSwift+Combine.swift | 6 +++++- .../ReactiveCocoaExtensions.swift | 5 ++++- .../ReactiveControlProtocol+Tapped.swift | 2 ++ .../ReactiveTapAction.swift | 2 ++ FueledUtils/SwiftUI/BackgroundBlur.swift | 6 ++++-- FueledUtils/SwiftUI/BlurView.swift | 10 ++++++---- FueledUtils/SwiftUI/EdgeInsets+Helpers.swift | 1 + FueledUtils/SwiftUI/ForEachWithIndex.swift | 4 ++++ FueledUtils/SwiftUI/FramePreferenceKey.swift | 1 + FueledUtils/SwiftUI/View+AnyView.swift | 1 + FueledUtils/UIKit/ControlProtocol.swift | 2 ++ Tests/Podfile.lock | 4 ++-- 43 files changed, 124 insertions(+), 22 deletions(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 5ae01320..b6b212f8 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -13,12 +13,12 @@ Pod::Spec.new do |s| s.source = { git: 'https://github.com/Fueled/ios-utilities.git', tag: s.version.to_s } s.documentation_url = 'https://cdn.rawgit.com/Fueled/ios-utilities/master/docs/index.html' - s.subspec 'Core' do |s| - s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.12' - s.watchos.deployment_target = '2.0' - s.tvos.deployment_target = '9.0' + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.12' + s.watchos.deployment_target = '2.0' + s.tvos.deployment_target = '9.0' + s.subspec 'Core' do |s| s.source_files = 'FueledUtils/Core/**/*.swift' end @@ -49,10 +49,12 @@ Pod::Spec.new do |s| end s.subspec 'Combine' do |s| - s.ios.deployment_target = '13.0' - s.osx.deployment_target = '10.15' - s.watchos.deployment_target = '6.0' - s.tvos.deployment_target = '13.0' + # Update the above with the following versions when we drop support for iOS < 13.0 or + # uncomment below if https://github.com/CocoaPods/CocoaPods/issues/7333 is implemented + # s.ios.deployment_target = '13.0' + # s.osx.deployment_target = '10.15' + # s.watchos.deployment_target = '6.0' + # s.tvos.deployment_target = '13.0' s.dependency 'FueledUtils/ReactiveCommon' diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 6e97dcd9..116c8641 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public final class Action { @Published public private(set) var isExecuting: Bool = false @Published public private(set) var isEnabled: Bool = false @@ -117,6 +118,7 @@ public final class Action { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Failure: ActionErrorProtocol { public func unwrappingActionError() -> AnyPublisher { self.catch { actionError -> AnyPublisher in @@ -129,6 +131,7 @@ extension Publisher where Failure: ActionErrorProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Action { public static func constant(_ value: Output) -> Action { self.constant(inputType: Input.self, value: value) diff --git a/FueledUtils/Combine/ActionError.swift b/FueledUtils/Combine/ActionError.swift index fbfe555e..f1cc075e 100644 --- a/FueledUtils/Combine/ActionError.swift +++ b/FueledUtils/Combine/ActionError.swift @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public enum ActionError: Swift.Error { case disabled case failure(Error) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ActionError: ActionErrorProtocol { public var innerError: Error? { if case .failure(let error) = self { @@ -26,6 +28,7 @@ extension ActionError: ActionErrorProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ActionError { public func map(_ mapper: (InnerError) -> NewError) -> ActionError { if let innerError = self.innerError { diff --git a/FueledUtils/Combine/ActionProtocol.swift b/FueledUtils/Combine/ActionProtocol.swift index ceccbb24..99a5ae7d 100644 --- a/FueledUtils/Combine/ActionProtocol.swift +++ b/FueledUtils/Combine/ActionProtocol.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol ActionProtocol { /// /// The type of the values used as inputs to the action. @@ -75,6 +76,7 @@ public protocol ActionProtocol { func apply(_ input: Input) -> ApplyPublisher } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Action: ActionProtocol { public typealias ApplyFailure = ActionError @@ -87,6 +89,7 @@ extension Action: ActionProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ActionProtocol where Input == Void { /// /// Create a `SignalProducer` that would attempt to create and start a unit of work of diff --git a/FueledUtils/Combine/AnyAction.swift b/FueledUtils/Combine/AnyAction.swift index 26db719c..f1caaf96 100644 --- a/FueledUtils/Combine/AnyAction.swift +++ b/FueledUtils/Combine/AnyAction.swift @@ -18,6 +18,7 @@ import Combine /// A type-erased Action that allows to store any `ActionProtocol` /// (loosing any type information at the same time) /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public final class AnyAction: ActionProtocol { public typealias Input = Any public typealias Output = Any diff --git a/FueledUtils/Combine/AnyCurrentValuePublisher.swift b/FueledUtils/Combine/AnyCurrentValuePublisher.swift index 924328f2..7f74f0a5 100644 --- a/FueledUtils/Combine/AnyCurrentValuePublisher.swift +++ b/FueledUtils/Combine/AnyCurrentValuePublisher.swift @@ -20,6 +20,7 @@ import Combine /// Use an `AnyCurrentValuePublisher` to wrap an existing current value publisher whose details you don’t want to expose. /// For example, this is useful if you want to use a `CurrentValueSubject` internally, but don't want to expose the setter/its send() method /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct AnyCurrentValuePublisher: CurrentValuePublisher { private let valueGetter: () -> Output private let receiveSubcriberClosure: (AnySubscriber) -> Void @@ -43,6 +44,7 @@ public struct AnyCurrentValuePublisher: CurrentVal } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CurrentValuePublisher { public func eraseToAnyCurrentValuePublisher() -> AnyCurrentValuePublisher { AnyCurrentValuePublisher(self) @@ -52,9 +54,11 @@ extension CurrentValuePublisher { /// /// A publisher that also stores the last value it sent /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol CurrentValuePublisher: Publisher { var value: Output { get } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CurrentValueSubject: CurrentValuePublisher { } diff --git a/FueledUtils/Combine/Binding+KeyPath.swift b/FueledUtils/Combine/Binding+KeyPath.swift index 8669b5f3..47725305 100644 --- a/FueledUtils/Combine/Binding+KeyPath.swift +++ b/FueledUtils/Combine/Binding+KeyPath.swift @@ -14,6 +14,7 @@ import SwiftUI +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Binding { public init(_ object: Type, to keyPath: ReferenceWritableKeyPath) { self.init( diff --git a/FueledUtils/Combine/CoalescingAction.swift b/FueledUtils/Combine/CoalescingAction.swift index f8abd0f0..63053f3d 100644 --- a/FueledUtils/Combine/CoalescingAction.swift +++ b/FueledUtils/Combine/CoalescingAction.swift @@ -23,6 +23,7 @@ import Combine /// calls to `apply()` when the action is executing, the inputs will be ignored until /// the action terminates. /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public class CoalescingAction: ActionProtocol { public typealias ApplyFailure = Failure diff --git a/FueledUtils/Combine/CombineExtensions+Cancellables.swift b/FueledUtils/Combine/CombineExtensions+Cancellables.swift index 72f73e04..85a425ff 100644 --- a/FueledUtils/Combine/CombineExtensions+Cancellables.swift +++ b/FueledUtils/Combine/CombineExtensions+Cancellables.swift @@ -16,6 +16,7 @@ import Combine private var cancellablesKey: UInt8 = 0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CombineExtensions { public var cancellables: Set { get { diff --git a/FueledUtils/Combine/CombineExtensions.swift b/FueledUtils/Combine/CombineExtensions.swift index 54a6899f..ea3b25cc 100644 --- a/FueledUtils/Combine/CombineExtensions.swift +++ b/FueledUtils/Combine/CombineExtensions.swift @@ -14,9 +14,11 @@ import Foundation +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol CombineExtensionsProvider { } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public final class CombineExtensions { var base: Base @@ -25,6 +27,7 @@ public final class CombineExtensions { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CombineExtensionsProvider { public var combineExtensions: CombineExtensions { return CombineExtensions(self) diff --git a/FueledUtils/Combine/NSObject+CombineExtensions.swift b/FueledUtils/Combine/NSObject+CombineExtensions.swift index fbf89850..2ab14c14 100644 --- a/FueledUtils/Combine/NSObject+CombineExtensions.swift +++ b/FueledUtils/Combine/NSObject+CombineExtensions.swift @@ -14,5 +14,6 @@ import Foundation +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension NSObject: CombineExtensionsProvider { } diff --git a/FueledUtils/Combine/ObservableObjectExtensions.swift b/FueledUtils/Combine/ObservableObjectExtensions.swift index 74d8eb49..51e94dfa 100644 --- a/FueledUtils/Combine/ObservableObjectExtensions.swift +++ b/FueledUtils/Combine/ObservableObjectExtensions.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher { // Perform a one-way link, where the receiver will listen for changes on the object and automatically trigger its `objectWillChange` publisher public func link(to object: Object) { @@ -74,6 +75,7 @@ extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObj } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ObservableObject { public var objectDidChange: AnyPublisher { // The delay of 0.0 allows the will to transform into a Did, by waiting for exactly one run loop cycle @@ -85,6 +87,7 @@ extension ObservableObject { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: Collection, Failure == Never, Output.Element: ObservableObject { public func onAnyChanges() -> AnyPublisher<[Output.Element], Never> { self.flatMap { Publishers.CombineLatestMany($0.map { $0.publisher }) }.eraseToAnyPublisher() diff --git a/FueledUtils/Combine/OverridingAction.swift b/FueledUtils/Combine/OverridingAction.swift index 0a1bdc43..af7c124c 100644 --- a/FueledUtils/Combine/OverridingAction.swift +++ b/FueledUtils/Combine/OverridingAction.swift @@ -18,6 +18,7 @@ import Combine /// Similar to `Action`, except if the action is already executing, subsequent `apply()` call will not fail, /// and will be interrupt the previous apply(). /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public class OverridingAction: ActionProtocol { public typealias ApplyFailure = Failure diff --git a/FueledUtils/Combine/Published+PublisherInit.swift b/FueledUtils/Combine/Published+PublisherInit.swift index 86f029ff..b3c45579 100644 --- a/FueledUtils/Combine/Published+PublisherInit.swift +++ b/FueledUtils/Combine/Published+PublisherInit.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Published { /// This method exists as it's currently impossible to use the projectedValue of a `@Published` property without having initialize the whole object, /// which is obviously not ideal if the projectedValue is required to initialize the whole object (basically a chicken and egg problem) diff --git a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift index 56e9137e..34c832cb 100644 --- a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift +++ b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift @@ -7,6 +7,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { /// - Parameters: /// - receiveTermination: Sent when the publisher either a completion event or is cancelled. diff --git a/FueledUtils/Combine/Publisher+CombinePrevious.swift b/FueledUtils/Combine/Publisher+CombinePrevious.swift index 7c79daf7..29555348 100644 --- a/FueledUtils/Combine/Publisher+CombinePrevious.swift +++ b/FueledUtils/Combine/Publisher+CombinePrevious.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { public func combinePrevious() -> AnyPublisher<(previous: Output, current: Output), Failure> { self.combinePreviousImplementation(nil) diff --git a/FueledUtils/Combine/Publisher+IgnoreLoading.swift b/FueledUtils/Combine/Publisher+IgnoreLoading.swift index 5b327c71..bb27adfd 100644 --- a/FueledUtils/Combine/Publisher+IgnoreLoading.swift +++ b/FueledUtils/Combine/Publisher+IgnoreLoading.swift @@ -15,6 +15,7 @@ import Combine import FueledUtils +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol TransferStateProtocol { associatedtype Progress associatedtype Value @@ -39,6 +40,7 @@ extension TransferState: TransferStateProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: TransferStateProtocol { public func ignoreLoading() -> AnyPublisher { self.flatMap { transferState -> AnyPublisher in diff --git a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift index a5fbdccf..dbf0939f 100644 --- a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift +++ b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift @@ -15,6 +15,7 @@ import Combine import FueledUtils +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { public func ignoreRepeats(isEqual: @escaping (Output, Output) -> Bool) -> AnyPublisher { self.map { Optional($0) } @@ -33,6 +34,7 @@ extension Publisher { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: Equatable { public func ignoreRepeats() -> AnyPublisher { self.ignoreRepeats(isEqual: ==) diff --git a/FueledUtils/Combine/PublisherExtensions.swift b/FueledUtils/Combine/PublisherExtensions.swift index 28ac6686..0d77e7b3 100644 --- a/FueledUtils/Combine/PublisherExtensions.swift +++ b/FueledUtils/Combine/PublisherExtensions.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { public func ignoreError() -> AnyPublisher { self.catch { _ in Empty() }.eraseToAnyPublisher() @@ -61,6 +62,7 @@ extension Publisher { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { public func performDuringLifetimeOf(_ object: Object, action: @escaping (Object, Output) -> Void) { self @@ -80,6 +82,7 @@ extension Publisher { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Failure == Never { public func assign(to keyPath: ReferenceWritableKeyPath, withoutRetaining object: Object) -> AnyCancellable { self.sink { [weak object] in @@ -95,6 +98,7 @@ extension Publisher where Failure == Never { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: OptionalProtocol { public func ignoreNil() -> AnyPublisher { self.flatMap { ($0.wrapped.map { Just($0).eraseToAnyPublisher() } ?? Empty().eraseToAnyPublisher()).setFailureType(to: Failure.self) }.eraseToAnyPublisher() diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index bc7c53d9..f2cfc02e 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publishers { public struct CombineLatestMany: Publisher where PublisherCollection.Element: Combine.Publisher { public typealias Output = [PublisherCollection.Element.Output] @@ -33,6 +34,7 @@ extension Publishers { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class CombineLatestManySubscription< Subscriber: Combine.Subscriber, PublisherCollection: Swift.Collection diff --git a/FueledUtils/Combine/Subject+SendResult.swift b/FueledUtils/Combine/Subject+SendResult.swift index b17f4bd3..66e0fa7b 100644 --- a/FueledUtils/Combine/Subject+SendResult.swift +++ b/FueledUtils/Combine/Subject+SendResult.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Subject { public func send(result: Result) { switch result { diff --git a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift index fbd14258..1873ee92 100644 --- a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift +++ b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift @@ -14,6 +14,7 @@ import Combine +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Subscriber { public func eraseToAnySubscriber() -> AnySubscriber { AnySubscriber(self) diff --git a/FueledUtils/CombineOperators/Combine+Operators.swift b/FueledUtils/CombineOperators/Combine+Operators.swift index 8815e7e1..1128e1fe 100644 --- a/FueledUtils/CombineOperators/Combine+Operators.swift +++ b/FueledUtils/CombineOperators/Combine+Operators.swift @@ -44,15 +44,18 @@ precedencegroup InsertCancellablePrecedence { infix operator >>>: InsertCancellablePrecedence +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct ObjectKeyPathReference { public let object: Root public let keyPath: ReferenceWritableKeyPath } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func ~ (lhs: Object, rhs: ReferenceWritableKeyPath) -> ObjectKeyPathReference { ObjectKeyPathReference(object: lhs, keyPath: rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func <~ ( lhs: ObjectKeyPathReference, rhs: Publisher @@ -60,6 +63,7 @@ public func <~ ( rhs.assign(to: lhs.keyPath, withoutRetaining: lhs.object) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func <~ ( lhs: ObservingObject, rhs: ObservedObject @@ -67,6 +71,7 @@ public func <~ ( lhs: ObservingObject, rhs: ObservedObjectCollection @@ -74,6 +79,7 @@ public func <~ ( lhs: ObservingObject, rhs: Publisher @@ -81,6 +87,7 @@ public func <~ lhs.link(to: rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func <~ ( lhs: ObservingObject, rhs: Publisher @@ -88,6 +95,7 @@ public func <~ lhs.link(to: rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func <~ ( lhs: ObservingObject, rhs: Publisher @@ -95,6 +103,7 @@ public func <~ lhs.link(to: rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func <~ ( lhs: ObservingObject, rhs: ReferenceWritableKeyPath @@ -102,6 +111,7 @@ public func <~ ( lhs: ObservingObject, rhs: ReferenceWritableKeyPath @@ -109,10 +119,12 @@ public func <~ >> (lhs: AnyCancellable, rhs: inout CancellableCollection) where CancellableCollection.Element == AnyCancellable { lhs.store(in: &rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable, rhs: inout Set) { lhs.store(in: &rhs) } diff --git a/FueledUtils/CombineOperators/CombineOperators+Optional.swift b/FueledUtils/CombineOperators/CombineOperators+Optional.swift index 8384f7a6..75f60213 100644 --- a/FueledUtils/CombineOperators/CombineOperators+Optional.swift +++ b/FueledUtils/CombineOperators/CombineOperators+Optional.swift @@ -15,22 +15,27 @@ import Combine import FueledUtils +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable?, rhs: inout CancellableCollection) where CancellableCollection.Element == AnyCancellable { lhs?.store(in: &rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable?, rhs: inout Set) { lhs?.store(in: &rhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable, rhs: inout CancellableCollection?) where CancellableCollection.Element == AnyCancellable { rhs?.append(lhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable, rhs: inout Set?) { rhs?.insert(lhs) } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable?, rhs: inout CancellableCollection?) where CancellableCollection.Element == AnyCancellable { guard let lhs = lhs else { return @@ -38,6 +43,7 @@ public func >>> (lhs: AnyCanc lhs >>> rhs } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable?, rhs: inout Set?) { guard let lhs = lhs else { return diff --git a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift index 61d4b7db..e804fdd2 100644 --- a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift +++ b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import Combine private var tapActionStorage: UInt8 = 0 private var tapActionKey: UInt8 = 0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ControlProtocol { /// /// The action to be triggered when the button is tapped. @@ -58,6 +60,7 @@ extension ControlProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class TapActionStorage { let tapAction: TapAction var cancellables = Set() @@ -66,3 +69,4 @@ private final class TapActionStorage { self.tapAction = tapAction } } +#endif diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 29ebb4d2..946ca4d5 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import Combine /// /// `TapAction` wraps a `ActionProtocol` for use by any `ControlProtocol`. +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) final class TapAction: NSObject { @objc static var selector: Selector { #selector(userDidTapControl(_:)) @@ -59,3 +61,4 @@ final class TapAction: NSObject { #endif } } +#endif diff --git a/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift index 91e7959f..0e79e2a9 100644 --- a/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift +++ b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import Combine import UIKit private var publisherControlEventsProcessorsHolderKey: UInt8 = 0 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ControlProtocol { public func publisherForControlEvents(_ controlEvents: UIControl.Event) -> AnyPublisher { let passthroughSubject = PassthroughSubject() @@ -51,6 +53,7 @@ extension ControlProtocol { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class PublisherControlEventsProcessorsHolder { private final class PublisherControlEventsProcessor: NSObject { weak var passthroughSubject: PassthroughSubject! @@ -86,3 +89,4 @@ private final class PublisherControlEventsProcessorsHolder { } } } +#endif diff --git a/FueledUtils/CombineUIKit/UITextInput+Combine.swift b/FueledUtils/CombineUIKit/UITextInput+Combine.swift index 11e81342..ec3e07c0 100644 --- a/FueledUtils/CombineUIKit/UITextInput+Combine.swift +++ b/FueledUtils/CombineUIKit/UITextInput+Combine.swift @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import Combine import UIKit +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CombineExtensions where Base: UITextInput, Base: ControlProtocol { var textValues: AnyPublisher { self.textPublisherForControlEvents([.editingDidEnd, .editingDidEndOnExit]) @@ -37,3 +39,4 @@ extension CombineExtensions where Base: UITextInput, Base: ControlProtocol { .eraseToAnyPublisher() } } +#endif diff --git a/FueledUtils/Core/AnyIdentifiable.swift b/FueledUtils/Core/AnyIdentifiable.swift index ec1e3650..f36516b1 100644 --- a/FueledUtils/Core/AnyIdentifiable.swift +++ b/FueledUtils/Core/AnyIdentifiable.swift @@ -15,7 +15,7 @@ /// /// A type-erased `Identifiable` object. /// -@available(iOS 13, *) +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct AnyIdentifiable: Identifiable { private let hashValueClosure: () -> AnyHashable diff --git a/FueledUtils/Core/Lock.swift b/FueledUtils/Core/Lock.swift index 1437daab..1c1de4b4 100644 --- a/FueledUtils/Core/Lock.swift +++ b/FueledUtils/Core/Lock.swift @@ -20,7 +20,7 @@ private protocol LockImplementation { mutating func unlock() } -@available(iOS 10.0, *) +@available(iOS 10.0, tvOS 10.0, watchOS 3.0, *) private struct UnfairLock: LockImplementation { private var unfairLock = os_unfair_lock_s() @@ -79,7 +79,7 @@ public final class Lock { private var lockImplementation: LockImplementation public init() { - if #available(iOS 10.0, *) { + if #available(iOS 10.0, tvOS 10.0, watchOS 3.0, *) { self.lockImplementation = UnfairLock() } else { self.lockImplementation = PThreadMutexLock() ?? CocoaLock() diff --git a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift index 6023f6d4..72bbd779 100644 --- a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift +++ b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift @@ -15,6 +15,7 @@ import Combine import ReactiveSwift +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { public var producer: SignalProducer { SignalProducer { observer, lifetime in @@ -35,6 +36,7 @@ extension Publisher { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Lifetime { @discardableResult public static func += (lhs: Lifetime, rhs: Cancellable?) -> Disposable? { @@ -42,12 +44,14 @@ extension Lifetime { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Disposable { public var cancellable: some Cancellable { DisposableCancellable(self) } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private struct DisposableCancellable: Cancellable { private let disposable: Disposable diff --git a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift index 4548944d..62bbb294 100644 --- a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift +++ b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift @@ -16,6 +16,7 @@ import Combine import ReactiveSwift // From https://github.com/ReactiveCocoa/ReactiveSwift/pull/776/files#diff-d8195adf8a5f3283e072483fd9699c90 +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension SignalProducerConvertible { public func eraseToAnyPublisher() -> AnyPublisher { self.publisher.eraseToAnyPublisher() @@ -26,6 +27,7 @@ extension SignalProducerConvertible { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publishers { public struct SignalProducerPublisher: Publisher { public let base: SignalProducer @@ -41,6 +43,7 @@ extension Publishers { } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class SignalProducerSubscription: Combine.Subscription { typealias Output = Subscriber.Input typealias Failure = Subscriber.Failure @@ -191,13 +194,14 @@ private final class SignalProducerSubscription: } } - +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Cancellable { var disposable: some Disposable { CancellableDisposable(self) } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private final class CancellableDisposable: Disposable { private let cancellable: Cancellable private(set) var isDisposed: Bool = false diff --git a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift index 95cc0e44..6998d928 100644 --- a/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift +++ b/FueledUtils/ReactiveSwift/ReactiveCocoaExtensions.swift @@ -15,8 +15,9 @@ import Foundation import ReactiveCocoa import ReactiveSwift -#if os(iOS) +#if canImport(UIKit) import UIKit +#elseif canImport(AppKit) #endif /// @@ -69,6 +70,7 @@ public func transitionContext( } #endif +#if !os(watchOS) extension Reactive where Base: NSLayoutConstraint { /// /// Set whether the constant is active or not in its hierarchy. @@ -77,6 +79,7 @@ extension Reactive where Base: NSLayoutConstraint { return makeBindingTarget { $0.isActive = $1 } } } +#endif #if os(iOS) extension Reactive where Base: UIView { diff --git a/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift index 0b366203..a2866b5f 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import ReactiveCocoa import ReactiveSwift @@ -67,3 +68,4 @@ private final class TapActionStorage { self.tapAction = tapAction } } +#endif diff --git a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift index 782dcd59..82d295e9 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import FueledUtils import ReactiveSwift @@ -57,3 +58,4 @@ final class ReactiveTapAction: NSObject { self.executeClosure(button as! Control) } } +#endif diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index 88d0d378..abf89959 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -14,8 +14,9 @@ import SwiftUI +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension View { - #if os(iOS) + #if canImport(UIKit) && !os(watchOS) public func backgroundBlur(style: UIBlurEffect.Style, color: Color? = nil) -> some View { ZStack { color @@ -24,7 +25,8 @@ extension View { .eraseToAnyView() } } - #else + #elseif canImport(AppKit) + @available(macOS 10.15, *) public func backgroundBlur( material: NSVisualEffectView.Material = .appearanceBased, blendingMode: NSVisualEffectView.BlendingMode = .behindWindow, diff --git a/FueledUtils/SwiftUI/BlurView.swift b/FueledUtils/SwiftUI/BlurView.swift index 69d0014d..d76829d7 100644 --- a/FueledUtils/SwiftUI/BlurView.swift +++ b/FueledUtils/SwiftUI/BlurView.swift @@ -13,13 +13,14 @@ // limitations under the License. import SwiftUI -#if os(iOS) +#if canImport(UIKit) import UIKit -#else +#elseif canImport(AppKit) import AppKit #endif -#if os(iOS) +#if canImport(UIKit) && !os(watchOS) +@available(iOS 13.0, tvOS 13.0, *) public struct BlurView: UIViewRepresentable { public let style: UIBlurEffect.Style @@ -35,7 +36,8 @@ public struct BlurView: UIViewRepresentable { visualEffectView.effect = UIBlurEffect(style: self.style) } } -#else +#elseif canImport(AppKit) +@available(macOS 10.15, *) public struct BlurView: NSViewRepresentable { public let material: NSVisualEffectView.Material public let blendingMode: NSVisualEffectView.BlendingMode diff --git a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift index 213882af..bdb0098d 100644 --- a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift +++ b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift @@ -14,6 +14,7 @@ import SwiftUI +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension EdgeInsets { public static var zero: EdgeInsets { EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 0.0) diff --git a/FueledUtils/SwiftUI/ForEachWithIndex.swift b/FueledUtils/SwiftUI/ForEachWithIndex.swift index 0e36901b..2dde1cfc 100644 --- a/FueledUtils/SwiftUI/ForEachWithIndex.swift +++ b/FueledUtils/SwiftUI/ForEachWithIndex.swift @@ -14,6 +14,7 @@ import SwiftUI +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct ForEachWithIndex: View { public var data: Data public var content: (_ index: Data.Index, _ element: Data.Element) -> Content @@ -41,15 +42,18 @@ public struct ForEachWithIndex Content) { self.init(data, id: \.id, content: content) } } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ForEachWithIndex: DynamicViewContent where Content: View { } +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private struct IndexInfo: Hashable { let index: Index let id: KeyPath diff --git a/FueledUtils/SwiftUI/FramePreferenceKey.swift b/FueledUtils/SwiftUI/FramePreferenceKey.swift index b8273119..f185c098 100644 --- a/FueledUtils/SwiftUI/FramePreferenceKey.swift +++ b/FueledUtils/SwiftUI/FramePreferenceKey.swift @@ -18,6 +18,7 @@ import SwiftUI /// Used to retrieve the frame of a view through a preference key. /// `TagType` is used to uniquely identify the view using the preference key. /// +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct FramePreferenceKey: PreferenceKey { public static var defaultValue: CGRect { .zero diff --git a/FueledUtils/SwiftUI/View+AnyView.swift b/FueledUtils/SwiftUI/View+AnyView.swift index fffcfeb1..cc403cac 100644 --- a/FueledUtils/SwiftUI/View+AnyView.swift +++ b/FueledUtils/SwiftUI/View+AnyView.swift @@ -14,6 +14,7 @@ import SwiftUI +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension View { public func eraseToAnyView() -> AnyView { AnyView(self) diff --git a/FueledUtils/UIKit/ControlProtocol.swift b/FueledUtils/UIKit/ControlProtocol.swift index 261f9af6..e9f869ef 100644 --- a/FueledUtils/UIKit/ControlProtocol.swift +++ b/FueledUtils/UIKit/ControlProtocol.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(UIKit) && !os(watchOS) import UIKit /// @@ -30,3 +31,4 @@ public protocol ControlLoadingProtocol: ControlProtocol { // Make all `UIControl` a `ControlProtocol` by default. extension UIControl: ControlProtocol { } +#endif diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index a302f39a..13098300 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -52,7 +52,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: 988e17c4f4aef059750c2e501368c24ca8172d25 + FueledUtils: bd2bcc4460ebf15f8b709cd59df78fe277df2f03 Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: 083ae559e6f588ce519cab412ea119b431c26a24 @@ -60,4 +60,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 32300607a3a6ea0867f9cc805c81c934b352b46c -COCOAPODS: 1.10.0.rc.1 +COCOAPODS: 1.10.0 From b6cd8b020c5a561dd31e894b4ea7d8c84223ac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 28 Oct 2020 11:58:27 -0400 Subject: [PATCH 63/89] chore(structure): move Binding extension from Combine subspec to SwiftUI --- FueledUtils/{Combine => SwiftUI}/Binding+KeyPath.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FueledUtils/{Combine => SwiftUI}/Binding+KeyPath.swift (100%) diff --git a/FueledUtils/Combine/Binding+KeyPath.swift b/FueledUtils/SwiftUI/Binding+KeyPath.swift similarity index 100% rename from FueledUtils/Combine/Binding+KeyPath.swift rename to FueledUtils/SwiftUI/Binding+KeyPath.swift From c05b6391c9f95f0115fc47faeba88da7e3f7f66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 11 Jan 2021 17:45:29 -0500 Subject: [PATCH 64/89] fix(combine-latest-many): update CombineLatestMany() to match behavior of CombineLatest() --- .../Combine/PublishersExtensions.swift | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index f2cfc02e..7b91f98f 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -53,7 +53,7 @@ private final class CombineLatestManySubscription< var cancellables: [AnyCancellable] = [] mutating func cancel() { - self.currentDemand = .none + self.currentDemand = Subscribers.Demand.none self.pendingValuesBuffer = [] self.cancellables.forEach { $0.cancel() } } @@ -71,39 +71,46 @@ private final class CombineLatestManySubscription< func request(_ demand: Subscribers.Demand) { if self.publishers.isEmpty { if demand > 0 { - self.subscriber.receive([]) + _ = self.subscriber.receive([]) } self.subscriber.receive(completion: .finished) return } - func sendValueIfPossible(_ value: Subscriber.Input, demandsState: inout DemandsState) { - if demandsState.currentDemand == nil { - // Cancelled - return + func sendPendingValuesIfPossible(demand: Subscribers.Demand, demandsState: inout DemandsState) -> Int? { + if demandsState.currentDemand != nil { + demandsState.currentDemand += demand + var valuesSent = 0 + while let firstValue = demandsState.pendingValuesBuffer.first, demandsState.currentDemand > 0 { + demandsState.pendingValuesBuffer.removeFirst() + sendValueIfPossible(firstValue, demandsState: &demandsState) + valuesSent += 1 + } + return valuesSent } + return nil + } + func sendValueIfPossible(_ value: Subscriber.Input, demandsState: inout DemandsState) { if demandsState.currentDemand == 0 { demandsState.pendingValuesBuffer.append(value) return } - self.subscriber.receive(value) + let demandedValues = self.subscriber.receive(value) + let sentValues = sendPendingValuesIfPossible( + demand: demandedValues, + demandsState: &demandsState + ) ?? 0 + let remainingDemand = demandedValues - sentValues + demandsState.currentDemand += remainingDemand demandsState.currentDemand -= 1 } let shouldReturn = self.$demandsState.modify { demandsState -> Bool in - if let currentDemand = demandsState.currentDemand { - demandsState.currentDemand += demand - while let firstValue = demandsState.pendingValuesBuffer.first, demandsState.currentDemand > 0 { - demandsState.pendingValuesBuffer.removeFirst() - sendValueIfPossible(firstValue, demandsState: &demandsState) - } - return true - } - + let sentValues = sendPendingValuesIfPossible(demand: demand, demandsState: &demandsState) demandsState.currentDemand = demand - return false + return sentValues != nil } if shouldReturn { From 9f29023e97a4add20121910c9e738b973e6d35ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 11 Jan 2021 16:25:17 -0500 Subject: [PATCH 65/89] chore(deployment-target): add proper support for MacOS for Combine extensions --- FueledUtils.podspec | 2 +- FueledUtils/Combine/Action.swift | 3 +++ FueledUtils/Combine/ActionProtocol.swift | 5 ++++- FueledUtils/Combine/AnyAction.swift | 3 +++ FueledUtils/Combine/AnyCurrentValuePublisher.swift | 3 +++ FueledUtils/Combine/CoalescingAction.swift | 3 +++ .../Combine/CombineExtensions+Cancellables.swift | 3 +++ .../Combine/ObservableObjectExtensions.swift | 3 +++ FueledUtils/Combine/OverridingAction.swift | 3 +++ FueledUtils/Combine/Published+PublisherInit.swift | 3 +++ .../Combine/Publisher+AdditionalHandleEvents.swift | 2 ++ .../Combine/Publisher+CombinePrevious.swift | 3 +++ FueledUtils/Combine/Publisher+IgnoreLoading.swift | 4 +++- FueledUtils/Combine/Publisher+IgnoreRepeats.swift | 4 +++- FueledUtils/Combine/PublisherExtensions.swift | 3 +++ FueledUtils/Combine/PublishersExtensions.swift | 3 +++ FueledUtils/Combine/Subject+SendResult.swift | 3 +++ .../Combine/Subscriber+EraseToAnySubscriber.swift | 3 +++ .../CombineOperators/Combine+Operators.swift | 3 +++ .../CombineOperators+Optional.swift | 4 +++- .../CombineUIKit/ControlProtocol+Tapped.swift | 3 ++- FueledUtils/CombineUIKit/TapAction.swift | 3 ++- .../UIControl+ControlEventsPublisher.swift | 4 +++- FueledUtils/CombineUIKit/UITextInput+Combine.swift | 3 ++- .../Combine+ReactiveSwift.swift | 3 +++ .../ReactiveSwift+Combine.swift | 3 +++ FueledUtils/ReactiveSwift/ReactiveAnyAction.swift | 1 - .../ReactiveSwift/ReactiveOverridingAction.swift | 1 - .../ReactiveSwift/TransferState+Reactive.swift | 2 +- .../ReactiveSwiftUIKit/ReactiveTapAction.swift | 1 - .../ReactiveSwiftUIKit/UIReactiveExtensions.swift | 1 - FueledUtils/SwiftUI/BackgroundBlur.swift | 3 +++ FueledUtils/SwiftUI/Binding+KeyPath.swift | 3 +++ FueledUtils/SwiftUI/BlurView.swift | 3 +++ FueledUtils/SwiftUI/EdgeInsets+Helpers.swift | 3 +++ FueledUtils/SwiftUI/ForEachWithIndex.swift | 3 +++ FueledUtils/SwiftUI/FramePreferenceKey.swift | 3 +++ FueledUtils/SwiftUI/View+AnyView.swift | 3 +++ Tests/FueledUtils.xcodeproj/project.pbxproj | 14 +++++++++++++- Tests/Podfile | 7 +++---- Tests/Podfile.lock | 4 ++-- Tests/Tests/CoalescingActionSpec.swift | 3 +++ Tests/Tests/CombineLatestManySpec.swift | 3 +++ Tests/Tests/OverridingActionSpec.swift | 3 +++ Tests/Tests/SinkForLifetimeSpec.swift | 3 +++ 45 files changed, 125 insertions(+), 21 deletions(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index b6b212f8..500e4bc5 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -71,7 +71,7 @@ Pod::Spec.new do |s| s.dependency 'FueledUtils/Combine' s.dependency 'FueledUtils/UIKit' - s.source_files = 'FueledUtils/CombineUIKit/**/*.swift' + s.ios.source_files = 'FueledUtils/CombineUIKit/**/*.swift' end s.subspec 'SwiftUI' do |s| diff --git a/FueledUtils/Combine/Action.swift b/FueledUtils/Combine/Action.swift index 116c8641..ed38326c 100644 --- a/FueledUtils/Combine/Action.swift +++ b/FueledUtils/Combine/Action.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -195,3 +196,5 @@ extension Action { return action } } + +#endif diff --git a/FueledUtils/Combine/ActionProtocol.swift b/FueledUtils/Combine/ActionProtocol.swift index 99a5ae7d..308e26f3 100644 --- a/FueledUtils/Combine/ActionProtocol.swift +++ b/FueledUtils/Combine/ActionProtocol.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -31,7 +32,7 @@ public protocol ActionProtocol { /// /// The type of errors emitted when applying the action. /// - associatedtype ApplyFailure: Swift.Error + associatedtype ApplyFailure associatedtype IsExecutingPublisher: Publisher where IsExecutingPublisher.Output == Bool, IsExecutingPublisher.Failure == Never associatedtype IsEnabledPublisher: Publisher where IsEnabledPublisher.Output == Bool, IsEnabledPublisher.Failure == Never @@ -104,3 +105,5 @@ extension ActionProtocol where Input == Void { return self.apply(()) } } + +#endif diff --git a/FueledUtils/Combine/AnyAction.swift b/FueledUtils/Combine/AnyAction.swift index f1caaf96..aaf67490 100644 --- a/FueledUtils/Combine/AnyAction.swift +++ b/FueledUtils/Combine/AnyAction.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine /// @@ -63,3 +64,5 @@ public final class AnyAction: ActionProtocol { self.applyClosure(input) } } + +#endif diff --git a/FueledUtils/Combine/AnyCurrentValuePublisher.swift b/FueledUtils/Combine/AnyCurrentValuePublisher.swift index 7f74f0a5..af94361b 100644 --- a/FueledUtils/Combine/AnyCurrentValuePublisher.swift +++ b/FueledUtils/Combine/AnyCurrentValuePublisher.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine /// @@ -62,3 +63,5 @@ public protocol CurrentValuePublisher: Publisher { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CurrentValueSubject: CurrentValuePublisher { } + +#endif diff --git a/FueledUtils/Combine/CoalescingAction.swift b/FueledUtils/Combine/CoalescingAction.swift index 63053f3d..3139b2f1 100644 --- a/FueledUtils/Combine/CoalescingAction.swift +++ b/FueledUtils/Combine/CoalescingAction.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine /// @@ -148,3 +149,5 @@ public class CoalescingAction: ActionProtoc .eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/CombineExtensions+Cancellables.swift b/FueledUtils/Combine/CombineExtensions+Cancellables.swift index 85a425ff..a01e7a85 100644 --- a/FueledUtils/Combine/CombineExtensions+Cancellables.swift +++ b/FueledUtils/Combine/CombineExtensions+Cancellables.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine private var cancellablesKey: UInt8 = 0 @@ -31,3 +32,5 @@ extension CombineExtensions { } } } + +#endif diff --git a/FueledUtils/Combine/ObservableObjectExtensions.swift b/FueledUtils/Combine/ObservableObjectExtensions.swift index 51e94dfa..ad2fe6e0 100644 --- a/FueledUtils/Combine/ObservableObjectExtensions.swift +++ b/FueledUtils/Combine/ObservableObjectExtensions.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -93,3 +94,5 @@ extension Publisher where Output: Collection, Failure == Never, Output.Element: self.flatMap { Publishers.CombineLatestMany($0.map { $0.publisher }) }.eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/OverridingAction.swift b/FueledUtils/Combine/OverridingAction.swift index af7c124c..e6bb0386 100644 --- a/FueledUtils/Combine/OverridingAction.swift +++ b/FueledUtils/Combine/OverridingAction.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine /// @@ -100,3 +101,5 @@ public class OverridingAction: ActionProtoc .eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/Published+PublisherInit.swift b/FueledUtils/Combine/Published+PublisherInit.swift index b3c45579..1ad428e4 100644 --- a/FueledUtils/Combine/Published+PublisherInit.swift +++ b/FueledUtils/Combine/Published+PublisherInit.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -46,3 +47,5 @@ extension Published { return publisher } } + +#endif diff --git a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift index 34c832cb..e4ba4c06 100644 --- a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift +++ b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift @@ -5,6 +5,7 @@ // Created by Stéphane Copin on 10/14/20. // +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -92,3 +93,4 @@ extension Publisher { } } +#endif diff --git a/FueledUtils/Combine/Publisher+CombinePrevious.swift b/FueledUtils/Combine/Publisher+CombinePrevious.swift index 29555348..392a4062 100644 --- a/FueledUtils/Combine/Publisher+CombinePrevious.swift +++ b/FueledUtils/Combine/Publisher+CombinePrevious.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -42,3 +43,5 @@ extension Publisher { .eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/Publisher+IgnoreLoading.swift b/FueledUtils/Combine/Publisher+IgnoreLoading.swift index bb27adfd..70ec4d62 100644 --- a/FueledUtils/Combine/Publisher+IgnoreLoading.swift +++ b/FueledUtils/Combine/Publisher+IgnoreLoading.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine -import FueledUtils @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public protocol TransferStateProtocol { @@ -55,3 +55,5 @@ extension Publisher where Output: TransferStateProtocol { .eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift index dbf0939f..204783cb 100644 --- a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift +++ b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine -import FueledUtils @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { @@ -40,3 +40,5 @@ extension Publisher where Output: Equatable { self.ignoreRepeats(isEqual: ==) } } + +#endif diff --git a/FueledUtils/Combine/PublisherExtensions.swift b/FueledUtils/Combine/PublisherExtensions.swift index 0d77e7b3..02e3cfa7 100644 --- a/FueledUtils/Combine/PublisherExtensions.swift +++ b/FueledUtils/Combine/PublisherExtensions.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -104,3 +105,5 @@ extension Publisher where Output: OptionalProtocol { self.flatMap { ($0.wrapped.map { Just($0).eraseToAnyPublisher() } ?? Empty().eraseToAnyPublisher()).setFailureType(to: Failure.self) }.eraseToAnyPublisher() } } + +#endif diff --git a/FueledUtils/Combine/PublishersExtensions.swift b/FueledUtils/Combine/PublishersExtensions.swift index 7b91f98f..3367e143 100644 --- a/FueledUtils/Combine/PublishersExtensions.swift +++ b/FueledUtils/Combine/PublishersExtensions.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -173,3 +174,5 @@ private final class CombineLatestManySubscription< self.$demandsState.modify { $0.cancel() } } } + +#endif diff --git a/FueledUtils/Combine/Subject+SendResult.swift b/FueledUtils/Combine/Subject+SendResult.swift index 66e0fa7b..cc90b186 100644 --- a/FueledUtils/Combine/Subject+SendResult.swift +++ b/FueledUtils/Combine/Subject+SendResult.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -26,3 +27,5 @@ extension Subject { } } } + +#endif diff --git a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift index 1873ee92..f37b5098 100644 --- a/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift +++ b/FueledUtils/Combine/Subscriber+EraseToAnySubscriber.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -20,3 +21,5 @@ extension Subscriber { AnySubscriber(self) } } + +#endif diff --git a/FueledUtils/CombineOperators/Combine+Operators.swift b/FueledUtils/CombineOperators/Combine+Operators.swift index 1128e1fe..6f03c618 100644 --- a/FueledUtils/CombineOperators/Combine+Operators.swift +++ b/FueledUtils/CombineOperators/Combine+Operators.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine // swiftlint:disable generic_type_name @@ -128,3 +129,5 @@ public func >>> (lhs: AnyCanc public func >>> (lhs: AnyCancellable, rhs: inout Set) { lhs.store(in: &rhs) } + +#endif diff --git a/FueledUtils/CombineOperators/CombineOperators+Optional.swift b/FueledUtils/CombineOperators/CombineOperators+Optional.swift index 75f60213..29888812 100644 --- a/FueledUtils/CombineOperators/CombineOperators+Optional.swift +++ b/FueledUtils/CombineOperators/CombineOperators+Optional.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine -import FueledUtils @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func >>> (lhs: AnyCancellable?, rhs: inout CancellableCollection) where CancellableCollection.Element == AnyCancellable { @@ -50,3 +50,5 @@ public func >>> (lhs: AnyCancellable?, rhs: inout Set?) { } lhs >>> rhs } + +#endif diff --git a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift index e804fdd2..0fd2303c 100644 --- a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift +++ b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) && !os(watchOS) && canImport(Combine) import Combine private var tapActionStorage: UInt8 = 0 @@ -69,4 +69,5 @@ private final class TapActionStorage { self.tapAction = tapAction } } + #endif diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 946ca4d5..7ff400d5 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) && !os(watchOS) && canImport(Combine) import Combine /// @@ -61,4 +61,5 @@ final class TapAction: NSObject { #endif } } + #endif diff --git a/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift index 0e79e2a9..c6a5a998 100644 --- a/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift +++ b/FueledUtils/CombineUIKit/UIControl+ControlEventsPublisher.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) && !os(watchOS) && canImport(Combine) import Combine import UIKit @@ -27,6 +27,7 @@ extension ControlProtocol { in: self, passthroughSubject: passthroughSubject ) + _ = cancellable return passthroughSubject .map { $0 as! Self @@ -89,4 +90,5 @@ private final class PublisherControlEventsProcessorsHolder { } } } + #endif diff --git a/FueledUtils/CombineUIKit/UITextInput+Combine.swift b/FueledUtils/CombineUIKit/UITextInput+Combine.swift index ec3e07c0..a9ffa7c1 100644 --- a/FueledUtils/CombineUIKit/UITextInput+Combine.swift +++ b/FueledUtils/CombineUIKit/UITextInput+Combine.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) && !os(watchOS) && canImport(Combine) import Combine import UIKit @@ -39,4 +39,5 @@ extension CombineExtensions where Base: UITextInput, Base: ControlProtocol { .eraseToAnyPublisher() } } + #endif diff --git a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift index 72bbd779..6a532a07 100644 --- a/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift +++ b/FueledUtils/ReactiveCombineBridge/Combine+ReactiveSwift.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine import ReactiveSwift @@ -63,3 +64,5 @@ private struct DisposableCancellable: Canc self.disposable.dispose() } } + +#endif diff --git a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift index 62bbb294..15624199 100644 --- a/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift +++ b/FueledUtils/ReactiveCombineBridge/ReactiveSwift+Combine.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine import ReactiveSwift @@ -215,3 +216,5 @@ private final class CancellableDisposable: Dis self.isDisposed = true } } + +#endif diff --git a/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift b/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift index 907f2960..075fb28d 100644 --- a/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift +++ b/FueledUtils/ReactiveSwift/ReactiveAnyAction.swift @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FueledUtils import ReactiveSwift /// diff --git a/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift b/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift index 497a20ae..e8a81c3d 100644 --- a/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift +++ b/FueledUtils/ReactiveSwift/ReactiveOverridingAction.swift @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FueledUtils import ReactiveSwift /// diff --git a/FueledUtils/ReactiveSwift/TransferState+Reactive.swift b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift index 3f1f9be4..91be0e0f 100644 --- a/FueledUtils/ReactiveSwift/TransferState+Reactive.swift +++ b/FueledUtils/ReactiveSwift/TransferState+Reactive.swift @@ -21,7 +21,7 @@ extension SignalProtocol { public func ignoreLoading() -> Signal where Self.Value == TransferState { - return self.signal.filterMap { status in + return self.signal.compactMap { status in switch status { case .loading: return nil diff --git a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift index 82d295e9..d9de4048 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift @@ -13,7 +13,6 @@ // limitations under the License. #if canImport(UIKit) && !os(watchOS) -import FueledUtils import ReactiveSwift /// diff --git a/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift index 86f6ebf5..4ecc9b23 100644 --- a/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift +++ b/FueledUtils/ReactiveSwiftUIKit/UIReactiveExtensions.swift @@ -13,7 +13,6 @@ // limitations under the License. import Foundation -import FueledUtils import ReactiveSwift import UIKit diff --git a/FueledUtils/SwiftUI/BackgroundBlur.swift b/FueledUtils/SwiftUI/BackgroundBlur.swift index abf89959..379daeb1 100644 --- a/FueledUtils/SwiftUI/BackgroundBlur.swift +++ b/FueledUtils/SwiftUI/BackgroundBlur.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -50,3 +51,5 @@ extension View { } #endif } + +#endif diff --git a/FueledUtils/SwiftUI/Binding+KeyPath.swift b/FueledUtils/SwiftUI/Binding+KeyPath.swift index 47725305..66fe50e3 100644 --- a/FueledUtils/SwiftUI/Binding+KeyPath.swift +++ b/FueledUtils/SwiftUI/Binding+KeyPath.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -27,3 +28,5 @@ extension Binding { ) } } + +#endif diff --git a/FueledUtils/SwiftUI/BlurView.swift b/FueledUtils/SwiftUI/BlurView.swift index d76829d7..6cbcdc7a 100644 --- a/FueledUtils/SwiftUI/BlurView.swift +++ b/FueledUtils/SwiftUI/BlurView.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI #if canImport(UIKit) import UIKit @@ -73,3 +74,5 @@ public struct BlurView: NSViewRepresentable { } #endif + +#endif diff --git a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift index bdb0098d..3352912d 100644 --- a/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift +++ b/FueledUtils/SwiftUI/EdgeInsets+Helpers.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -33,3 +34,5 @@ extension EdgeInsets { ) } } + +#endif diff --git a/FueledUtils/SwiftUI/ForEachWithIndex.swift b/FueledUtils/SwiftUI/ForEachWithIndex.swift index 2dde1cfc..9e2e8767 100644 --- a/FueledUtils/SwiftUI/ForEachWithIndex.swift +++ b/FueledUtils/SwiftUI/ForEachWithIndex.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -71,3 +72,5 @@ private struct IndexInfo: Hashable { self.elementID.hash(into: &hasher) } } + +#endif diff --git a/FueledUtils/SwiftUI/FramePreferenceKey.swift b/FueledUtils/SwiftUI/FramePreferenceKey.swift index f185c098..812564b9 100644 --- a/FueledUtils/SwiftUI/FramePreferenceKey.swift +++ b/FueledUtils/SwiftUI/FramePreferenceKey.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI /// @@ -28,3 +29,5 @@ public struct FramePreferenceKey: PreferenceKey { value = nextValue() } } + +#endif diff --git a/FueledUtils/SwiftUI/View+AnyView.swift b/FueledUtils/SwiftUI/View+AnyView.swift index cc403cac..49ecd3f4 100644 --- a/FueledUtils/SwiftUI/View+AnyView.swift +++ b/FueledUtils/SwiftUI/View+AnyView.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(SwiftUI) import SwiftUI @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @@ -20,3 +21,5 @@ extension View { AnyView(self) } } + +#endif diff --git a/Tests/FueledUtils.xcodeproj/project.pbxproj b/Tests/FueledUtils.xcodeproj/project.pbxproj index 113108e0..d7d2f3df 100644 --- a/Tests/FueledUtils.xcodeproj/project.pbxproj +++ b/Tests/FueledUtils.xcodeproj/project.pbxproj @@ -20,15 +20,21 @@ /* Begin PBXFileReference section */ 00B6C3B54CADD6729AF81F36 /* Pods-Common-FueledUtilsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.release.xcconfig"; sourceTree = ""; }; + 1A3B52FAD952809D407DDA1E /* Pods_Common_asd_WatchKit_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_asd_WatchKit_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig"; sourceTree = ""; }; 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_FueledUtilsTests_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 48DE7B611288E2A6C99EEDAE /* Pods_Common_asd_WatchKit_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Common_asd_WatchKit_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A32E8FB94B9CE2642794723 /* Pods-Common-asd WatchKit Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-asd WatchKit Extension.release.xcconfig"; path = "Target Support Files/Pods-Common-asd WatchKit Extension/Pods-Common-asd WatchKit Extension.release.xcconfig"; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* FueledUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FueledUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65B56BF0A7147538E12F737F /* Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-SwiftUI/Pods-Common-FueledUtilsTests-SwiftUI.debug.xcconfig"; sourceTree = ""; }; + AB25EAABC0CDD547AE307879 /* Pods-Common-asd WatchKit App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-asd WatchKit App.release.xcconfig"; path = "Target Support Files/Pods-Common-asd WatchKit App/Pods-Common-asd WatchKit App.release.xcconfig"; sourceTree = ""; }; ACF665E90E66C2B35C6C5C05 /* Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.release.xcconfig"; sourceTree = ""; }; + C783847C92189B0968E12A16 /* Pods-Common-asd WatchKit Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-asd WatchKit Extension.debug.xcconfig"; path = "Target Support Files/Pods-Common-asd WatchKit Extension/Pods-Common-asd WatchKit Extension.debug.xcconfig"; sourceTree = ""; }; CF40A2CC4151F8E9B373D243 /* FueledUtils.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FueledUtils.podspec; path = ../FueledUtils.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests/Pods-Common-FueledUtilsTests.debug.xcconfig"; sourceTree = ""; }; D498AEE5BC8A0A514416E6DA /* Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; path = "Target Support Files/Pods-Common-FueledUtilsTests-ReactiveSwift/Pods-Common-FueledUtilsTests-ReactiveSwift.debug.xcconfig"; sourceTree = ""; }; E7AC5BA00D7F6054AC66E468 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + E82CDABCC774F7F31D52705D /* Pods-Common-asd WatchKit App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Common-asd WatchKit App.debug.xcconfig"; path = "Target Support Files/Pods-Common-asd WatchKit App/Pods-Common-asd WatchKit App.debug.xcconfig"; sourceTree = ""; }; F429742925378348004BFA85 /* ReactiveOverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveOverridingActionSpec.swift; sourceTree = ""; }; F429742D25378425004BFA85 /* OverridingActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridingActionSpec.swift; sourceTree = ""; }; F453AA7A2538DE55008F045B /* CombineLatestManySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineLatestManySpec.swift; sourceTree = ""; }; @@ -63,6 +69,10 @@ 3297A6B2D4B923F8F27CD6A5 /* Pods-Common-FueledUtilsTests-SwiftUI.release.xcconfig */, D43FD9799A872E88963939C1 /* Pods-Common-FueledUtilsTests.debug.xcconfig */, 00B6C3B54CADD6729AF81F36 /* Pods-Common-FueledUtilsTests.release.xcconfig */, + C783847C92189B0968E12A16 /* Pods-Common-asd WatchKit Extension.debug.xcconfig */, + 4A32E8FB94B9CE2642794723 /* Pods-Common-asd WatchKit Extension.release.xcconfig */, + E82CDABCC774F7F31D52705D /* Pods-Common-asd WatchKit App.debug.xcconfig */, + AB25EAABC0CDD547AE307879 /* Pods-Common-asd WatchKit App.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -102,6 +112,8 @@ children = ( 39EF9AE56A510B26CCC488B7 /* Pods_Common_FueledUtilsTests_SwiftUI.framework */, F46CDEA885028D8DB189B3CB /* Pods_Common_FueledUtilsTests.framework */, + 1A3B52FAD952809D407DDA1E /* Pods_Common_asd_WatchKit_Extension.framework */, + 48DE7B611288E2A6C99EEDAE /* Pods_Common_asd_WatchKit_App.framework */, ); name = Frameworks; sourceTree = ""; @@ -157,7 +169,7 @@ 607FACC81AFB9204008FA782 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1140; + LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1140; ORGANIZATIONNAME = CocoaPods; TargetAttributes = { diff --git a/Tests/Podfile b/Tests/Podfile index d02cad75..100fe2ae 100644 --- a/Tests/Podfile +++ b/Tests/Podfile @@ -1,13 +1,12 @@ -inhibit_all_warnings! use_frameworks! abstract_target 'Common' do - pod 'Quick', '~> 2.0' - pod 'Nimble', '~> 8.0' - target 'FueledUtilsTests' do platform :ios, '13.0' + pod 'Quick', '~> 2.0' + pod 'Nimble', '~> 8.0' + pod 'FueledUtils/ReactiveCombineBridge', path: '../' pod 'FueledUtils/ReactiveSwift', path: '../' pod 'FueledUtils/ReactiveSwiftUIKit', path: '../' diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index 13098300..d67c157c 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -52,12 +52,12 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - FueledUtils: bd2bcc4460ebf15f8b709cd59df78fe277df2f03 + FueledUtils: aa4b2a1e780f4f7e870ad53b64d0d7d16ab78604 Nimble: 4ab1aeb9b45553c75b9687196b0fa0713170a332 Quick: 7fb19e13be07b5dfb3b90d4f9824c855a11af40e ReactiveCocoa: 083ae559e6f588ce519cab412ea119b431c26a24 ReactiveSwift: 7555791a608c0679563a3f72546f971b2a06de98 -PODFILE CHECKSUM: 32300607a3a6ea0867f9cc805c81c934b352b46c +PODFILE CHECKSUM: 57c718acafcbfdffd0d4c0cd376a0088c9cc4812 COCOAPODS: 1.10.0 diff --git a/Tests/Tests/CoalescingActionSpec.swift b/Tests/Tests/CoalescingActionSpec.swift index 2797a078..f3027130 100644 --- a/Tests/Tests/CoalescingActionSpec.swift +++ b/Tests/Tests/CoalescingActionSpec.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine import FueledUtils import Quick @@ -56,3 +57,5 @@ class CoalescingActionSpec: QuickSpec { } } } + +#endif diff --git a/Tests/Tests/CombineLatestManySpec.swift b/Tests/Tests/CombineLatestManySpec.swift index 20ce9c2d..80125cfd 100644 --- a/Tests/Tests/CombineLatestManySpec.swift +++ b/Tests/Tests/CombineLatestManySpec.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine import FueledUtils import Quick @@ -190,3 +191,5 @@ class CombineLatestManySpec: QuickSpec { } } } + +#endif diff --git a/Tests/Tests/OverridingActionSpec.swift b/Tests/Tests/OverridingActionSpec.swift index 74ae12ed..35c77e6c 100644 --- a/Tests/Tests/OverridingActionSpec.swift +++ b/Tests/Tests/OverridingActionSpec.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if canImport(Combine) import Combine import FueledUtils import Quick @@ -62,3 +63,5 @@ class OverridingActionSpec: QuickSpec { } } } + +#endif diff --git a/Tests/Tests/SinkForLifetimeSpec.swift b/Tests/Tests/SinkForLifetimeSpec.swift index 56291f3b..ea1253a2 100644 --- a/Tests/Tests/SinkForLifetimeSpec.swift +++ b/Tests/Tests/SinkForLifetimeSpec.swift @@ -16,6 +16,7 @@ import Quick import Nimble import FueledUtils import ReactiveSwift +#if canImport(Combine) import Combine class SinkForLifetimeSpec: QuickSpec { @@ -49,3 +50,5 @@ class SinkForLifetimeSpec: QuickSpec { } } } + +#endif From 62a459264ce01583941c53832a7f8e1fce0f6287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 11 Jan 2021 16:35:29 -0500 Subject: [PATCH 66/89] chore(combine): make some internal-only methods & objects public --- .../CombineUIKit/ControlProtocol+Tapped.swift | 2 +- FueledUtils/CombineUIKit/TapAction.swift | 13 +++++++------ FueledUtils/CombineUIKit/UITextInput+Combine.swift | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift index 0fd2303c..80c5b9c1 100644 --- a/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift +++ b/FueledUtils/CombineUIKit/ControlProtocol+Tapped.swift @@ -26,7 +26,7 @@ extension ControlProtocol { /// protocol to represents the button rather than hardcode it to classes, /// allowing for any `UIControl` to use this method. /// - var tapped: TapAction? { + public var tapped: TapAction? { get { self.tapActionStorage?.tapAction } diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 7ff400d5..189551c6 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(UIKit) && !os(watchOS) && canImport(Combine) +#if canImport(UIKit) && !os(watchOS) +#if canImport(Combine) import Combine +#endif /// /// `TapAction` wraps a `ActionProtocol` for use by any `ControlProtocol`. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -final class TapAction: NSObject { +public final class TapAction: NSObject { @objc static var selector: Selector { #selector(userDidTapControl(_:)) } @@ -33,15 +35,15 @@ final class TapAction: NSObject { private let action: AnyAction private var cancellables = Set() - convenience init(_ action: Action) where Action.Input == Void { + public convenience init(_ action: Action) where Action.Input == Void { self.init(action, input: ()) } - convenience init(_ action: Action, input: Action.Input) { + public convenience init(_ action: Action, input: Action.Input) { self.init(action) { _ in input } } - init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { + public init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { self.isEnabled = action.isEnabled self.isExecuting = action.isExecuting self.inputTransform = { inputTransform($0) } @@ -61,5 +63,4 @@ final class TapAction: NSObject { #endif } } - #endif diff --git a/FueledUtils/CombineUIKit/UITextInput+Combine.swift b/FueledUtils/CombineUIKit/UITextInput+Combine.swift index a9ffa7c1..aef9a4dd 100644 --- a/FueledUtils/CombineUIKit/UITextInput+Combine.swift +++ b/FueledUtils/CombineUIKit/UITextInput+Combine.swift @@ -18,11 +18,11 @@ import UIKit @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension CombineExtensions where Base: UITextInput, Base: ControlProtocol { - var textValues: AnyPublisher { + public var textValues: AnyPublisher { self.textPublisherForControlEvents([.editingDidEnd, .editingDidEndOnExit]) } - var continuousTextValues: AnyPublisher { + public var continuousTextValues: AnyPublisher { self.textPublisherForControlEvents(.allEditingEvents) } From 44de5f207a9fd1312b988dc21364024e74202956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 11 Jan 2021 17:37:44 -0500 Subject: [PATCH 67/89] fix(tap-action): fix segfault in TapAction --- FueledUtils/CombineUIKit/TapAction.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 189551c6..29b2a49b 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -20,7 +20,7 @@ import Combine /// /// `TapAction` wraps a `ActionProtocol` for use by any `ControlProtocol`. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -public final class TapAction: NSObject { +public final class TapAction: NSObject { @objc static var selector: Selector { #selector(userDidTapControl(_:)) } @@ -30,7 +30,6 @@ public final class TapAction: NSObject { // FIXME: (Stéphane) To be retested for the next version of Swift (after 5.3) // Any initializers below create a segfault when compiling with optimizations. - #if false private let inputTransform: ((Control) -> Any) private let action: AnyAction private var cancellables = Set() @@ -54,7 +53,7 @@ public final class TapAction: NSObject { self.action.isExecutingPublisher.assign(to: \.isExecuting, withoutRetaining: self) .store(in: &self.cancellables) } - #endif + @objc private func userDidTapControl(_ button: Any) { #if false From fa7621cd6bd17c2aa656a025b67c809a914d8fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 11 Jan 2021 17:57:34 -0500 Subject: [PATCH 68/89] chore(version): bump version to 3.0-alpha2 --- FueledUtils.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 500e4bc5..44f82818 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'FueledUtils' - s.version = '3.0-alpha1' + s.version = '3.0-alpha2' s.summary = 'A collection of utilities used at Fueled' s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' s.swift_version = '5' From 33e79d44b58bb5cdd6e440d3a76f964c3d559850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 12 Jan 2021 19:37:41 -0500 Subject: [PATCH 69/89] fix(combine-extensions/cancellables): fix crash when trying to modify existing attached cancellables object due to AnyCancellable not bridging to NSObject at runtime --- .../CombineExtensions+Cancellables.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/FueledUtils/Combine/CombineExtensions+Cancellables.swift b/FueledUtils/Combine/CombineExtensions+Cancellables.swift index a01e7a85..0e73cd3c 100644 --- a/FueledUtils/Combine/CombineExtensions+Cancellables.swift +++ b/FueledUtils/Combine/CombineExtensions+Cancellables.swift @@ -21,12 +21,29 @@ private var cancellablesKey: UInt8 = 0 extension CombineExtensions { public var cancellables: Set { get { - objc_getAssociatedObject(self.base, &cancellablesKey) as? Set ?? { + self.cancellablesHelper.map { Set($0.map(\.cancellable)) } ?? { let cancellables = Set() self.cancellables = cancellables return cancellables }() } + set { + self.cancellablesHelper = newValue.map { CancellableHolder($0) } + } + } + + private final class CancellableHolder: NSObject { + let cancellable: AnyCancellable + + init(_ cancellable: AnyCancellable) { + self.cancellable = cancellable + } + } + + private var cancellablesHelper: [CancellableHolder]? { + get { + objc_getAssociatedObject(self.base, &cancellablesKey) as? [CancellableHolder] + } set { objc_setAssociatedObject(self.base, &cancellablesKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY) } From c6d1dce3a3ca380379d6ba735f25fb9666790aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 20 Jan 2021 17:42:02 -0500 Subject: [PATCH 70/89] chore(tap-action): remove #if false from TapAction --- FueledUtils/CombineUIKit/TapAction.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 29b2a49b..79709dd3 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -56,10 +56,8 @@ public final class TapAction: NSObject { @objc private func userDidTapControl(_ button: Any) { - #if false self.action.apply(self.inputTransform(button as! Control)).sink() .store(in: &self.cancellables) - #endif } } #endif From 160396ca336d9e2bfdfc33962ff3162f9b21a71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Thu, 21 Jan 2021 18:14:07 -0500 Subject: [PATCH 71/89] chore(version): bump version to 3.0-alpha3 --- FueledUtils.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index 44f82818..d5fdbba2 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'FueledUtils' - s.version = '3.0-alpha2' + s.version = '3.0-alpha3' s.summary = 'A collection of utilities used at Fueled' s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' s.swift_version = '5' From b1b0987755da74f56fb8701fec0a8859f5e54594 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 13:11:35 +0000 Subject: [PATCH 72/89] chore(deps): bump kramdown from 2.3.0 to 2.3.1 Bumps [kramdown](https://github.com/gettalong/kramdown) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/gettalong/kramdown/releases) - [Changelog](https://github.com/gettalong/kramdown/blob/master/doc/news.page) - [Commits](https://github.com/gettalong/kramdown/commits) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 04b9318a..3fbc4a04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,7 +30,7 @@ GEM faraday (>= 0.8) git (1.7.0) rchardet (~> 1.8) - kramdown (2.3.0) + kramdown (2.3.1) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) From c7fae062fc756ad9aa9393d24efe673f2795c18e Mon Sep 17 00:00:00 2001 From: Benoit Layer <1849419+notbenoit@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:56:04 +0100 Subject: [PATCH 73/89] fix(tap-action): change tap action visibility --- .../ReactiveControlProtocol+Tapped.swift | 2 +- .../ReactiveSwiftUIKit/ReactiveTapAction.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift index a2866b5f..7c9912d5 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveControlProtocol+Tapped.swift @@ -26,7 +26,7 @@ extension Reactive where Base: ControlProtocol { /// protocol to represents the button rather than hardcode it to classes, /// allowing for any `UIControl` to use this method. /// - var tapped: ReactiveTapAction? { + public var tapped: ReactiveTapAction? { get { self.tapActionStorage?.tapAction } diff --git a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift index d9de4048..705bfda1 100644 --- a/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift +++ b/FueledUtils/ReactiveSwiftUIKit/ReactiveTapAction.swift @@ -20,25 +20,25 @@ import ReactiveSwift /// This is a mirror of `CococaAction` in `ReactiveCocoa`, allowing to use a /// `ButtonProtocol` and assigin /// -final class ReactiveTapAction: NSObject { +public final class ReactiveTapAction: NSObject { @objc static var selector: Selector { #selector(userDidTapControl(_:)) } - let isExecuting: Property - let isEnabled: Property + public let isExecuting: Property + public let isEnabled: Property private let executeClosure: (Control) -> Void - convenience init(_ action: Action) where Action.Input == Void { + public convenience init(_ action: Action) where Action.Input == Void { self.init(action, input: ()) } - convenience init(_ action: Action, input: Action.Input) { + public convenience init(_ action: Action, input: Action.Input) { self.init(action) { _ in input } } - init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { + public init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { self.executeClosure = { action.apply(inputTransform($0)).start() } From e4dfe77c9873afed035fb5c4519fb9789863874a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Tue, 6 Apr 2021 10:11:52 -0400 Subject: [PATCH 74/89] fix(publisher/ignore-repeats): fix combine previous & ignore repeats working only on one Publisher --- .../Combine/Publisher+CombinePrevious.swift | 14 +++++++------- .../Combine/Publisher+IgnoreRepeats.swift | 18 ++++-------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/FueledUtils/Combine/Publisher+CombinePrevious.swift b/FueledUtils/Combine/Publisher+CombinePrevious.swift index 392a4062..f49320c8 100644 --- a/FueledUtils/Combine/Publisher+CombinePrevious.swift +++ b/FueledUtils/Combine/Publisher+CombinePrevious.swift @@ -26,20 +26,20 @@ extension Publisher { } private func combinePreviousImplementation(_ initial: Output?) -> AnyPublisher<(previous: Output, current: Output), Failure> { - var previousValue = initial return self - .flatMap { output -> AnyPublisher<(previous: Output, current: Output), Failure> in - defer { - previousValue = output - } - if let currentPreviousValue = previousValue { - return Just((currentPreviousValue, output)) + .scan((Output?.none, initial)) { current, newValue in + (current.1, newValue) + } + .map { previous, current -> AnyPublisher<(previous: Output, current: Output), Failure> in + if let previous = previous { + return Just((previous, current!)) .setFailureType(to: Failure.self) .eraseToAnyPublisher() } else { return Empty(completeImmediately: false).eraseToAnyPublisher() } } + .switchToLatest() .eraseToAnyPublisher() } } diff --git a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift index 204783cb..a20eef1d 100644 --- a/FueledUtils/Combine/Publisher+IgnoreRepeats.swift +++ b/FueledUtils/Combine/Publisher+IgnoreRepeats.swift @@ -17,27 +17,17 @@ import Combine @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher { + @available(*, deprecated, renamed: "removeDuplicates(by:)") public func ignoreRepeats(isEqual: @escaping (Output, Output) -> Bool) -> AnyPublisher { - self.map { Optional($0) } - .combinePrevious(nil) - .flatMap { previous, current -> AnyPublisher in - let current = current! - if previous.map({ isEqual($0, current) }) ?? false { - return Empty(completeImmediately: false) - .eraseToAnyPublisher() - } - return Just(current) - .setFailureType(to: Failure.self) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() + self.removeDuplicates(by: isEqual).eraseToAnyPublisher() } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: Equatable { + @available(*, deprecated, renamed: "removeDuplicates(by:)") public func ignoreRepeats() -> AnyPublisher { - self.ignoreRepeats(isEqual: ==) + self.removeDuplicates(by: ==).eraseToAnyPublisher() } } From 10a42d0f795e9875b065d0e1b69fb6b1ff17f5da Mon Sep 17 00:00:00 2001 From: Rob Deans Date: Wed, 21 Apr 2021 12:18:06 -0400 Subject: [PATCH 75/89] chore(collections): move collections extensions to be accessible from non-UIKit projects --- FueledUtils/{UIKit => Core}/CollectionExtensions.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FueledUtils/{UIKit => Core}/CollectionExtensions.swift (100%) diff --git a/FueledUtils/UIKit/CollectionExtensions.swift b/FueledUtils/Core/CollectionExtensions.swift similarity index 100% rename from FueledUtils/UIKit/CollectionExtensions.swift rename to FueledUtils/Core/CollectionExtensions.swift From 9fa5e64b0462399f9f1f8e3a5e1b54e175f5be52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 23 Apr 2021 14:16:20 -0400 Subject: [PATCH 76/89] fix(combine/extensions): fix combine extensions visibility --- FueledUtils/Combine/CombineExtensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FueledUtils/Combine/CombineExtensions.swift b/FueledUtils/Combine/CombineExtensions.swift index ea3b25cc..4bcc183e 100644 --- a/FueledUtils/Combine/CombineExtensions.swift +++ b/FueledUtils/Combine/CombineExtensions.swift @@ -20,9 +20,9 @@ public protocol CombineExtensionsProvider { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public final class CombineExtensions { - var base: Base + public var base: Base - init(_ base: Base) { + fileprivate init(_ base: Base) { self.base = base } } From ed897a76a7cb5edf61f384f06901b42754cc1668 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 21:30:46 +0000 Subject: [PATCH 77/89] chore(deps): bump rexml from 3.2.4 to 3.2.5 Bumps [rexml](https://github.com/ruby/rexml) from 3.2.4 to 3.2.5. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.2.4...v3.2.5) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3fbc4a04..09abe42b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -43,7 +43,7 @@ GEM open4 (1.3.4) public_suffix (4.0.6) rchardet (1.8.0) - rexml (3.2.4) + rexml (3.2.5) sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) From 1c5756151aae2c428c3ea18a2691c85bb736b086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 4 Jun 2021 00:09:15 -0400 Subject: [PATCH 78/89] fix(transfer-state): make `map` function rethrows --- FueledUtils/Core/TransferState.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FueledUtils/Core/TransferState.swift b/FueledUtils/Core/TransferState.swift index 4dbb89d0..678016e0 100644 --- a/FueledUtils/Core/TransferState.swift +++ b/FueledUtils/Core/TransferState.swift @@ -45,12 +45,12 @@ public enum TransferState { /// - mapper: Mapper closure how to map the initial value to the mapped value. /// - Returns: A `TransferState` with the mapped value as mapped by the given closure. /// - public func map(_ mapper: (Value) -> Mapped) -> TransferState { + public func map(_ mapper: (Value) throws -> Mapped) rethrows -> TransferState { switch self { case .loading(let progress): return .loading(progress) case .finished(let result): - return .finished(mapper(result)) + return .finished(try mapper(result)) } } } From 676a95c96333a9f5517ed7737099f0c923468cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 4 Jun 2021 00:10:03 -0400 Subject: [PATCH 79/89] refactor(transfer-state): move TransferStateProtocol into TransferState.swift and add interpolate method --- ...ng.swift => Publisher+TransferState.swift} | 28 +------------ .../Combine/TransferState+Combine.swift | 39 ++++++++++++++++++ FueledUtils/Core/TransferState.swift | 41 ++++++++++++++++--- 3 files changed, 77 insertions(+), 31 deletions(-) rename FueledUtils/Combine/{Publisher+IgnoreLoading.swift => Publisher+TransferState.swift} (66%) create mode 100644 FueledUtils/Combine/TransferState+Combine.swift diff --git a/FueledUtils/Combine/Publisher+IgnoreLoading.swift b/FueledUtils/Combine/Publisher+TransferState.swift similarity index 66% rename from FueledUtils/Combine/Publisher+IgnoreLoading.swift rename to FueledUtils/Combine/Publisher+TransferState.swift index 70ec4d62..fa553bcb 100644 --- a/FueledUtils/Combine/Publisher+IgnoreLoading.swift +++ b/FueledUtils/Combine/Publisher+TransferState.swift @@ -15,35 +15,10 @@ #if canImport(Combine) import Combine -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -public protocol TransferStateProtocol { - associatedtype Progress - associatedtype Value - - var progress: Progress? { get } - var value: Value? { get } -} - -extension TransferState: TransferStateProtocol { - public var progress: Progress? { - if case .loading(let progress) = self { - return progress - } - return nil - } - - public var value: Value? { - if case .finished(let value) = self { - return value - } - return nil - } -} - @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Publisher where Output: TransferStateProtocol { public func ignoreLoading() -> AnyPublisher { - self.flatMap { transferState -> AnyPublisher in + self.map { transferState -> AnyPublisher in guard let value = transferState.value else { return Empty(completeImmediately: true) .eraseToAnyPublisher() @@ -52,6 +27,7 @@ extension Publisher where Output: TransferStateProtocol { .setFailureType(to: Failure.self) .eraseToAnyPublisher() } + .switchToLatest() .eraseToAnyPublisher() } } diff --git a/FueledUtils/Combine/TransferState+Combine.swift b/FueledUtils/Combine/TransferState+Combine.swift new file mode 100644 index 00000000..712cef4b --- /dev/null +++ b/FueledUtils/Combine/TransferState+Combine.swift @@ -0,0 +1,39 @@ +// Copyright © 2020, Fueled Digital Media, LLC +// +// 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. + +#if canImport(Combine) +import Combine + +//@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) +//extension TransferState { +// public func mapPublisher(mapper: ) -> AnyPublisher, Error> { +// switch transferState { +// case .loading: +// return Just(transferState.interpolated(min: 0.0, max: 0.85).map { _ in }) +// .setFailureType(to: APIError.self) +// .eraseToAnyPublisher() +// case .finished(let archiveURL): +// let destinationURL = APICharacter.localURL(coreDataCharacter) +// return self.storeCharacterFromArchiveURL( +// archiveURL, +// destinationURL: destinationURL, +// temporaryURL: temporaryURL +// ) +// .map { $0.interpolated(min: 0.85, max: 1.0).map { _ in } } +// .eraseToAnyPublisher() +// } +// } +//} + +#endif diff --git a/FueledUtils/Core/TransferState.swift b/FueledUtils/Core/TransferState.swift index 678016e0..36b7f034 100644 --- a/FueledUtils/Core/TransferState.swift +++ b/FueledUtils/Core/TransferState.swift @@ -14,6 +14,14 @@ import Foundation +public protocol TransferStateProtocol { + associatedtype Progress + associatedtype Value + + var progress: Progress? { get } + var value: Value? { get } +} + /// /// Represents a transfer state, either loading or finished. /// This is useful to represent a transfer state in percentage or bytes uploaded for example. @@ -28,7 +36,7 @@ import Foundation /// ``` /// Nothing prevents you to also include the `Value` type in the `typealias`, if possible for your use case. /// -public enum TransferState { +public enum TransferState: TransferStateProtocol { /// /// Represents a `loading` state. /// @@ -38,6 +46,22 @@ public enum TransferState { /// case finished(Value) + public var progress: Progress? { + if case .loading(let progress) = self { + return progress + } + return nil + } + + public var value: Value? { + if case .finished(let value) = self { + return value + } + return nil + } +} + +extension TransferStateProtocol { /// /// Map a `TransferState` finishing with one `Value` into another, mapping it with the given closure. /// @@ -46,11 +70,18 @@ public enum TransferState { /// - Returns: A `TransferState` with the mapped value as mapped by the given closure. /// public func map(_ mapper: (Value) throws -> Mapped) rethrows -> TransferState { - switch self { - case .loading(let progress): + if let progress = self.progress { return .loading(progress) - case .finished(let result): - return .finished(try mapper(result)) } + return .finished(try mapper(self.value!)) + } +} + +extension TransferStateProtocol where Progress: Numeric { + public func interpolated(min: Progress, max: Progress) -> TransferState { + if let progress = self.progress { + return .loading(progress * (max - min) + min) + } + return .finished(self.value!) } } From aacd6c14233e7eb2454e619f54d57e4476322fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 7 Jun 2021 21:12:40 -0400 Subject: [PATCH 80/89] chore(combine/action/apply): make ActionProtocol.apply() for Void input public --- FueledUtils/Combine/ActionProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils/Combine/ActionProtocol.swift b/FueledUtils/Combine/ActionProtocol.swift index 308e26f3..9c047963 100644 --- a/FueledUtils/Combine/ActionProtocol.swift +++ b/FueledUtils/Combine/ActionProtocol.swift @@ -101,7 +101,7 @@ extension ActionProtocol where Input == Void { /// or returns an appropriate `ApplyError` indicating the specific error /// that happened. /// - func apply() -> ApplyPublisher { + public func apply() -> ApplyPublisher { return self.apply(()) } } From 72c09f39318812b837115ed930aae70fed061d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Mon, 7 Jun 2021 21:13:52 -0400 Subject: [PATCH 81/89] feat(combine/tap-action): add confirmAction closure allowing users to add confirm action to an action if necessary --- FueledUtils/CombineUIKit/TapAction.swift | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/FueledUtils/CombineUIKit/TapAction.swift b/FueledUtils/CombineUIKit/TapAction.swift index 79709dd3..c36e8d2e 100644 --- a/FueledUtils/CombineUIKit/TapAction.swift +++ b/FueledUtils/CombineUIKit/TapAction.swift @@ -31,21 +31,23 @@ public final class TapAction: NSObject { // FIXME: (Stéphane) To be retested for the next version of Swift (after 5.3) // Any initializers below create a segfault when compiling with optimizations. private let inputTransform: ((Control) -> Any) + private let confirmAction: ((@escaping () -> Void) -> Void)? private let action: AnyAction private var cancellables = Set() - public convenience init(_ action: Action) where Action.Input == Void { - self.init(action, input: ()) + public convenience init(_ action: Action, confirmAction: ((@escaping () -> Void) -> Void)? = nil) where Action.Input == Void { + self.init(action, input: (), confirmAction: confirmAction) } - public convenience init(_ action: Action, input: Action.Input) { - self.init(action) { _ in input } + public convenience init(_ action: Action, input: Action.Input, confirmAction: ((@escaping () -> Void) -> Void)? = nil) { + self.init(action, confirmAction: confirmAction, inputTransform: { _ in input }) } - public init(_ action: Action, inputTransform: @escaping (Control) -> Action.Input) { + public init(_ action: Action, confirmAction: (((@escaping () -> Void) -> Void))? = nil, inputTransform: @escaping (Control) -> Action.Input) { self.isEnabled = action.isEnabled self.isExecuting = action.isExecuting self.inputTransform = { inputTransform($0) } + self.confirmAction = confirmAction self.action = AnyAction(action) super.init() self.action.isEnabledPublisher.assign(to: \.isEnabled, withoutRetaining: self) @@ -54,10 +56,16 @@ public final class TapAction: NSObject { .store(in: &self.cancellables) } - @objc private func userDidTapControl(_ button: Any) { - self.action.apply(self.inputTransform(button as! Control)).sink() - .store(in: &self.cancellables) + let confirmAction = self.confirmAction ?? { $0() } + confirmAction { [weak self] in + guard let self = self else { + return + } + + self.action.apply(self.inputTransform(button as! Control)).sink() + .store(in: &self.cancellables) + } } } #endif From 4d7cb4a1fd3c1e8fb65b082bda970ef9bb71856c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jul 2021 15:48:33 +0000 Subject: [PATCH 82/89] chore(deps): bump addressable from 2.7.0 to 2.8.0 Bumps [addressable](https://github.com/sporkmonger/addressable) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/sporkmonger/addressable/releases) - [Changelog](https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md) - [Commits](https://github.com/sporkmonger/addressable/compare/addressable-2.7.0...addressable-2.8.0) --- updated-dependencies: - dependency-name: addressable dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 09abe42b..e1e33c95 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) claide (1.0.3) claide-plugins (0.9.2) From 60298a614d9096bad9b5a16897727176808b3908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 28 Jul 2021 15:39:09 -0400 Subject: [PATCH 83/89] fix(atomic): fix Atomic & AtomicValue struct which read access would not actually be atomic --- .../Combine/TransferState+Combine.swift | 39 ------------------- FueledUtils/Core/Atomic.swift | 24 ++++++++++-- 2 files changed, 20 insertions(+), 43 deletions(-) delete mode 100644 FueledUtils/Combine/TransferState+Combine.swift diff --git a/FueledUtils/Combine/TransferState+Combine.swift b/FueledUtils/Combine/TransferState+Combine.swift deleted file mode 100644 index 712cef4b..00000000 --- a/FueledUtils/Combine/TransferState+Combine.swift +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2020, Fueled Digital Media, LLC -// -// 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. - -#if canImport(Combine) -import Combine - -//@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -//extension TransferState { -// public func mapPublisher(mapper: ) -> AnyPublisher, Error> { -// switch transferState { -// case .loading: -// return Just(transferState.interpolated(min: 0.0, max: 0.85).map { _ in }) -// .setFailureType(to: APIError.self) -// .eraseToAnyPublisher() -// case .finished(let archiveURL): -// let destinationURL = APICharacter.localURL(coreDataCharacter) -// return self.storeCharacterFromArchiveURL( -// archiveURL, -// destinationURL: destinationURL, -// temporaryURL: temporaryURL -// ) -// .map { $0.interpolated(min: 0.85, max: 1.0).map { _ in } } -// .eraseToAnyPublisher() -// } -// } -//} - -#endif diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift index f3021510..f8993568 100644 --- a/FueledUtils/Core/Atomic.swift +++ b/FueledUtils/Core/Atomic.swift @@ -17,11 +17,27 @@ import Foundation infix operator .=: AssignmentPrecedence public final class AtomicValue { - private(set) var value: Value + private var valueStorage: Value + private(set) var value: Value { + get { + self.lock.lock() + defer { + self.lock.unlock() + } + return self.valueStorage + } + set { + self.lock.lock() + defer { + self.lock.unlock() + } + self.valueStorage = newValue + } + } private let lock = Lock() public init(_ value: Value) { - self.value = value + self.valueStorage = value } public static func .= (atomicValue: AtomicValue, value: Value) { @@ -33,7 +49,7 @@ public final class AtomicValue { defer { self.lock.unlock() } - return modify(&self.value) + return modify(&self.valueStorage) } public func withValue(_ getter: (Value) -> Return) -> Return { @@ -41,7 +57,7 @@ public final class AtomicValue { defer { self.lock.unlock() } - return getter(self.value) + return getter(self.valueStorage) } } From 480258d0eda5fc9ce70b6854cc6a560ab9f84480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Wed, 28 Jul 2021 15:44:30 -0400 Subject: [PATCH 84/89] refactor(atomic): make Atomic & AtomicValue internal value with a warning documentation comment --- FueledUtils/Core/Atomic.swift | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/FueledUtils/Core/Atomic.swift b/FueledUtils/Core/Atomic.swift index f8993568..18ebb986 100644 --- a/FueledUtils/Core/Atomic.swift +++ b/FueledUtils/Core/Atomic.swift @@ -18,7 +18,22 @@ infix operator .=: AssignmentPrecedence public final class AtomicValue { private var valueStorage: Value - private(set) var value: Value { + + /// If modifying the `value`, consider that the operation might not be atomic. Consider the following examples: + /// ``` + /// let atomicValue = AtomicValue(0) + /// atomicValue.value = 1 + /// ``` + /// This is an atomic operation as the original value is overriden. + /// ``` + /// let atomicValue = AtomicValue(0) + /// atomicValue.value += 1 + /// ``` + /// This is _not_ an atomic operation, as this will be translated as: + /// atomicValue.value = atomicValue.value + 1 + /// Resulting in the internal lock being acquired twice, resulting in potential undesired behavior. + /// If modifying the value, `modify()` is recommended to avoid such behavior. + public var value: Value { get { self.lock.lock() defer { @@ -74,7 +89,12 @@ public struct Atomic { } public var wrappedValue: Value { - self.atomicValue.value + get { + self.atomicValue.value + } + set { + self.atomicValue.value = newValue + } } public var projectedValue: Atomic { From 788a8990a0ed69f24be0a21c2ee50e155effaabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Copin?= Date: Fri, 13 Aug 2021 15:30:44 -0400 Subject: [PATCH 85/89] fix(combine/additional-events): fix termination that could be sent twice --- .../Combine/Publisher+AdditionalHandleEvents.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift index e4ba4c06..ad55aaf3 100644 --- a/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift +++ b/FueledUtils/Combine/Publisher+AdditionalHandleEvents.swift @@ -71,7 +71,17 @@ extension Publisher { receiveResult: ((Result) -> Void)?, receiveRequest: ((Subscribers.Demand) -> Void)? = nil ) -> Publishers.HandleEvents { - self.handleEvents( + var hasTerminated = false + let receiveTermination = receiveTermination.map { receiveTermination in + { + if hasTerminated { + return + } + hasTerminated = true + receiveTermination() + } + } + return self.handleEvents( receiveSubscription: receiveSubscription, receiveOutput: { receiveOutput?($0) From e3fa73526c29164d3c983830d29ec385ae2525cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 22:14:27 +0000 Subject: [PATCH 86/89] chore(deps): bump git from 1.7.0 to 1.11.0 Bumps [git](https://github.com/ruby-git/ruby-git) from 1.7.0 to 1.11.0. - [Release notes](https://github.com/ruby-git/ruby-git/releases) - [Changelog](https://github.com/ruby-git/ruby-git/blob/master/CHANGELOG.md) - [Commits](https://github.com/ruby-git/ruby-git/compare/v1.7.0...v1.11.0) --- updated-dependencies: - dependency-name: git dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e1e33c95..42b17db2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,7 +28,7 @@ GEM multipart-post (>= 1.2, < 3) faraday-http-cache (2.2.0) faraday (>= 0.8) - git (1.7.0) + git (1.11.0) rchardet (~> 1.8) kramdown (2.3.1) rexml From 127e3cd394e5f6974a415beaef6315a65b26f33f Mon Sep 17 00:00:00 2001 From: Benoit Layer <1849419+notbenoit@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:18:00 +0200 Subject: [PATCH 87/89] docs(changelog): rename master branch name to main --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c5e481..c2c3544a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## Main ##### New Features/Enhancements From f834ad0fab9dc76a74d1ec131295485f4227f1cc Mon Sep 17 00:00:00 2001 From: Benoit Layer <1849419+notbenoit@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:25:58 +0200 Subject: [PATCH 88/89] docs(contributing): update contribution guidelines as the xcworkspace moved to the Test folder the doc needed to be updated --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c550fe76..307aa6be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ In order to properly clone the project and be ready to submit bug fixes/new feat ```shell git submodule update --init --recursive ``` -2. Open `FueledUtils.xcworkspace` +2. Open `Test/FueledUtils.xcworkspace` and update the *FueledUtils* Pod in `Development Pods` 3. You're ready to go! ## How to contribute From 85483469ae75400608684856cee758024b105c34 Mon Sep 17 00:00:00 2001 From: Benoit Layer <1849419+notbenoit@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:15:19 +0200 Subject: [PATCH 89/89] chore(project): bump podspec version to 3.0.0 --- FueledUtils.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FueledUtils.podspec b/FueledUtils.podspec index d5fdbba2..3966e47b 100644 --- a/FueledUtils.podspec +++ b/FueledUtils.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = 'FueledUtils' - s.version = '3.0-alpha3' + s.version = '3.0.0' s.summary = 'A collection of utilities used at Fueled' s.description = 'This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.' s.swift_version = '5'