diff --git a/.gitignore b/.gitignore index a119ed5..c4d34a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store # Xcode +.swiftpm build/ *.pbxuser !default.pbxuser diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b37768b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -# references: -# * http://www.objc.io/issue-6/travis-ci.html -# * https://github.com/supermarin/xcpretty#usage - -osx_image: xcode11 -language: swift -# cache: cocoapods -# podfile: Example/Podfile -# before_install: -# - gem install cocoapods # Since Travis is not always on latest version -# - pod install --project-directory=Example -script: -- pod lib lint diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..d0356c9 --- /dev/null +++ b/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:5.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SimpleTwoWayBinding", + platforms: [.iOS(.v10)], + products: [ + .library( + name: "SimpleTwoWayBinding", + targets: ["SimpleTwoWayBinding"]), + ], + dependencies: [ ], + targets: [ + .target( + name: "SimpleTwoWayBinding", + dependencies: []), + .testTarget( + name: "SimpleTwoWayBindingTests", + dependencies: ["SimpleTwoWayBinding"]), + ] +) diff --git a/SimpleTwoWayBinding.podspec b/SimpleTwoWayBinding.podspec deleted file mode 100644 index 61fdf52..0000000 --- a/SimpleTwoWayBinding.podspec +++ /dev/null @@ -1,19 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'SimpleTwoWayBinding' - s.version = '0.0.7' - s.summary = 'Ultra light weight and simple two way binding for iOS UIControls.' - s.description = <<-DESC -Ultra light weight and simple two way binding for UIControls. -Written with love and hope in Swift 5. - DESC - - s.homepage = 'https://github.com/manishkkatoch/SimpleTwoWayBindingIOS' - s.license = { :type => 'MIT', :file => 'LICENSE' } - s.author = { 'Manish Katoch' => 'manish.katoch@gmail.com' } - s.source = { :git => 'https://github.com/manishkkatoch/SimpleTwoWayBindingIOS.git', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - - s.source_files = 'Sources/**/*' - s.frameworks = 'UIKit' - s.swift_version = '5.0' -end diff --git a/Sources/BlockBasedSelector/BlockBasedSelector.h b/Sources/BlockBasedSelector/BlockBasedSelector.h deleted file mode 100644 index 479b313..0000000 --- a/Sources/BlockBasedSelector/BlockBasedSelector.h +++ /dev/null @@ -1,17 +0,0 @@ -// BlockBasedSelector.h -// -// Created by Charlton Provatas on 11/2/17. -// Copyright © 2017 CharltonProvatas. All rights reserved. -// - -#import - -@interface BlockBasedSelector : NSObject - -@end - -typedef void (^OBJCBlock)(id foo); - -void class_addMethodWithBlock(Class class, SEL newSelector, OBJCBlock block); - - diff --git a/Sources/BlockBasedSelector/BlockBasedSelector.m b/Sources/BlockBasedSelector/BlockBasedSelector.m deleted file mode 100644 index 24fbf86..0000000 --- a/Sources/BlockBasedSelector/BlockBasedSelector.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// BlockBasedSelector.m -// -// Created by Charlton Provatas on 11/2/17. -// Copyright © 2017 CharltonProvatas. All rights reserved. -// - -#import "BlockBasedSelector.h" -#import - -@implementation BlockBasedSelector -@end - -void class_addMethodWithBlock(Class class, SEL newSelector, OBJCBlock block) -{ - IMP newImplementation = imp_implementationWithBlock(block); - Method method = class_getInstanceMethod(class, newSelector); - class_addMethod(class, newSelector, newImplementation, method_getTypeEncoding(method)); -} diff --git a/Sources/BlockBasedSelector/BlockBasedSelector.swift b/Sources/BlockBasedSelector/BlockBasedSelector.swift deleted file mode 100644 index 3303eb2..0000000 --- a/Sources/BlockBasedSelector/BlockBasedSelector.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// BlockBasedSelector.swift -// -// Created by Charlton Provatas on 11/2/17. -// Copyright © 2017 CharltonProvatas. All rights reserved. - -import Foundation -import UIKit - -func Selector(_ block: @escaping () -> Void) -> Selector { - let selector = NSSelectorFromString("\(CACurrentMediaTime())") - class_addMethodWithBlock(_Selector.self, selector) { (_) in block() } - return selector -} - -let Selector = _Selector.shared -@objc class _Selector: NSObject { - static let shared = _Selector() -} diff --git a/Sources/Bindable.swift b/Sources/SimpleTwoWayBinding/Bindable.swift similarity index 69% rename from Sources/Bindable.swift rename to Sources/SimpleTwoWayBinding/Bindable.swift index 91a2ddd..cec1e2e 100644 --- a/Sources/Bindable.swift +++ b/Sources/SimpleTwoWayBinding/Bindable.swift @@ -55,15 +55,44 @@ extension Bindable where Self: NSObject { @discardableResult public func bind(with observable: Observable) -> BindingReceipt { - if let _self = self as? UIControl { - _self.addTarget(Selector, action: Selector{ [weak self] in self?.valueChanged() }, for: [.editingChanged, .valueChanged]) + + if let control = self as? UIControl { + + let memoryAddress = String(format: "%p", unsafeBitCast(self, to: UInt.self)) + let sleeve = ActionClosure { [weak self] in + self?.valueChanged() + } + + control.addTarget(sleeve, + action: #selector(ActionClosure.invoke), + for: [.valueChanged, .editingChanged]) + + objc_setAssociatedObject(control, memoryAddress, sleeve, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + self.binder = observable if let val = observable.value { self.updateValue(with: val) } + return self.observe(for: observable) { (value) in self.updateValue(with: value) } - } + } +} + + +private extension NSObject { + + @objc final class ActionClosure: NSObject { + private let closure: () -> Void + + init(_ closure: @escaping () -> Void) { + self.closure = closure + } + + @objc func invoke() { + closure() + } + } } diff --git a/Sources/NSObject+Observable.swift b/Sources/SimpleTwoWayBinding/NSObject+Observable.swift similarity index 89% rename from Sources/NSObject+Observable.swift rename to Sources/SimpleTwoWayBinding/NSObject+Observable.swift index 2410965..2897dcb 100644 --- a/Sources/NSObject+Observable.swift +++ b/Sources/SimpleTwoWayBinding/NSObject+Observable.swift @@ -9,7 +9,7 @@ import Foundation extension NSObject { @discardableResult - public func observe(for observable: Observable, with: @escaping (T) -> ()) -> BindingReceipt { + public func observe(for observable: Observable, with: @escaping (T) -> Void) -> BindingReceipt { observable.bind { observable, value in DispatchQueue.main.async { with(value) diff --git a/Sources/Observable+FunctionalConvenience.swift b/Sources/SimpleTwoWayBinding/Observable+FunctionalConvenience.swift similarity index 100% rename from Sources/Observable+FunctionalConvenience.swift rename to Sources/SimpleTwoWayBinding/Observable+FunctionalConvenience.swift diff --git a/Sources/Observable.swift b/Sources/SimpleTwoWayBinding/Observable.swift similarity index 97% rename from Sources/Observable.swift rename to Sources/SimpleTwoWayBinding/Observable.swift index c74f378..b3f9b9f 100644 --- a/Sources/Observable.swift +++ b/Sources/SimpleTwoWayBinding/Observable.swift @@ -16,40 +16,40 @@ public struct BindingReceipt: Hashable, Identifiable { public class Observable { public typealias Observer = (_ observable: Observable, ObservedType) -> Void - + /// Map of receipt objects to the binding blocks those objects represent; see bind(observer:) and unbind(:) private var observers: [BindingReceipt: Observer] = [:] /// Map of other observers we've been bound to; see map(:) & other functional conveniences. This allows us to hold strong references to the anonymous observables generated in a chained series of calls, and break them when needed. private var bindings: [BindingReceipt: () -> Void] = [:] - + internal var paused: Bool = false - + public var value: ObservedType? { didSet { fire() } } - + /// Notify all observers with the current value if non-nil. public func fire() { if let value = value { notifyObservers(value) } } - + public init(_ value: ObservedType? = nil) { self.value = value } - + @discardableResult public func bind(observer: @escaping Observer) -> BindingReceipt { let r = BindingReceipt() observers[r] = observer return r } - + public func setObserving(_ referenceHolder: @escaping () -> Void, receipt: BindingReceipt) { bindings[receipt] = referenceHolder } - + public func unbind(_ r: BindingReceipt) { guard observers[r] != nil else { print("Warning: attempted to unbind with an invalid receipt") @@ -58,15 +58,14 @@ public class Observable { observers[r] = nil bindings[r] = nil } - + internal func notifyObservers(_ value: ObservedType) { observers.values.forEach { [unowned self] observer in guard paused == false else { return } observer(self, value) } } - - - -} + + +} diff --git a/Sources/PausableObservable.swift b/Sources/SimpleTwoWayBinding/PausableObservable.swift similarity index 99% rename from Sources/PausableObservable.swift rename to Sources/SimpleTwoWayBinding/PausableObservable.swift index 2bdf9b8..5ad4570 100644 --- a/Sources/PausableObservable.swift +++ b/Sources/SimpleTwoWayBinding/PausableObservable.swift @@ -5,7 +5,7 @@ // Created by Ryan Forsythe on 6/15/20. // -import Foundation +import UIKit /// A helper class to manage pausable receipts public class ReceiptBag { diff --git a/Sources/UIControls+Bindable.swift b/Sources/SimpleTwoWayBinding/UIControls+Bindable.swift similarity index 92% rename from Sources/UIControls+Bindable.swift rename to Sources/SimpleTwoWayBinding/UIControls+Bindable.swift index 133578c..105cef7 100644 --- a/Sources/UIControls+Bindable.swift +++ b/Sources/SimpleTwoWayBinding/UIControls+Bindable.swift @@ -5,9 +5,9 @@ // Created by Manish Katoch on 11/26/17. // -import Foundation +import UIKit -extension UITextField : Bindable { +extension UITextField: Bindable { public typealias BindingType = String public func observingValue() -> String? { @@ -21,7 +21,7 @@ extension UITextField : Bindable { } } -extension UISwitch : Bindable { +extension UISwitch: Bindable { public typealias BindingType = Bool public func observingValue() -> Bool? { @@ -33,7 +33,7 @@ extension UISwitch : Bindable { } } -extension UISlider : Bindable { +extension UISlider: Bindable { public typealias BindingType = Float public func observingValue() -> Float? { @@ -45,7 +45,7 @@ extension UISlider : Bindable { } } -extension UIStepper : Bindable { +extension UIStepper: Bindable { public typealias BindingType = Double public func observingValue() -> Double? { @@ -57,7 +57,7 @@ extension UIStepper : Bindable { } } -extension UISegmentedControl : Bindable { +extension UISegmentedControl: Bindable { public typealias BindingType = Int public func observingValue() -> Int? { diff --git a/Sources/SimpleTwoWayBindings-Bridging-Header.h b/Sources/SimpleTwoWayBindings-Bridging-Header.h deleted file mode 100644 index 10c68b0..0000000 --- a/Sources/SimpleTwoWayBindings-Bridging-Header.h +++ /dev/null @@ -1,8 +0,0 @@ -// -// SimpleTwoWayBindings-Bridging-Header.h -// Pods -// -// Created by Manich Katoch on 11/26/17. -// - -#import "BlockBasedSelector.h" diff --git a/Tests/SimpleTwoWayBindingTests.swift b/Tests/SimpleTwoWayBindingTests/SimpleTwoWayBindingTests.swift similarity index 100% rename from Tests/SimpleTwoWayBindingTests.swift rename to Tests/SimpleTwoWayBindingTests/SimpleTwoWayBindingTests.swift