diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index c747f5ff..c1336d20 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; }; EAB1D2CF28ABEF2B00DAE764 /* Typography.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */; }; EAB1D2E628AE842000DAE764 /* Publisher+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */; }; + EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */; }; EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* Checkbox.swift */; }; EAF7F0962899861000B287F5 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0942899861000B287F5 /* CheckboxModel.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; @@ -128,6 +129,7 @@ EAB1D2CC28ABE76000DAE764 /* Withable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Withable.swift; sourceTree = ""; }; EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typography.swift; sourceTree = ""; }; EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Publisher+Bind.swift"; sourceTree = ""; }; + EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControlPublisher.swift; sourceTree = ""; }; EAF7F0932899861000B287F5 /* Checkbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAF7F0942899861000B287F5 /* CheckboxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxModel.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; @@ -374,6 +376,7 @@ children = ( EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */, EAB1D2C628A6E76300DAE764 /* ModelHandlerPublisher.swift */, + EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */, ); path = Publishers; sourceTree = ""; @@ -560,6 +563,7 @@ EA33624728931B050071C351 /* Initable.swift in Sources */, EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */, EAF7F0B1289B177F00B287F5 /* LabelAttributeColor.swift in Sources */, + EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAF7F13328A2A16500B287F5 /* LabelAttributeAttachment.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, diff --git a/VDS/Publishers/UIControlPublisher.swift b/VDS/Publishers/UIControlPublisher.swift new file mode 100644 index 00000000..616bd8ca --- /dev/null +++ b/VDS/Publishers/UIControlPublisher.swift @@ -0,0 +1,73 @@ +// +// UIControlPublisher.swift +// VDS +// +// Created by Matt Bruce on 8/18/22. +// + +import Foundation +import UIKit +import Combine + +/// A custom subscription to capture UIControl target events. +final class UIControlSubscription: Subscription where SubscriberType.Input == Control { + private var subscriber: SubscriberType? + private let control: Control + + init(subscriber: SubscriberType, control: Control, event: UIControl.Event) { + self.subscriber = subscriber + self.control = control + control.addTarget(self, action: #selector(eventHandler), for: event) + } + + func request(_ demand: Subscribers.Demand) { + // We do nothing here as we only want to send events when they occur. + // See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand + } + + func cancel() { + subscriber = nil + } + + @objc private func eventHandler() { + _ = subscriber?.receive(control) + } + + deinit { + print("UIControlTarget deinit") + } +} + +/// A custom `Publisher` to work with our custom `UIControlSubscription`. +struct UIControlPublisher: Publisher { + + typealias Output = Control + typealias Failure = Never + + let control: Control + let controlEvents: UIControl.Event + + init(control: Control, events: UIControl.Event) { + self.control = control + self.controlEvents = events + } + + /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` + /// + /// - SeeAlso: `subscribe(_:)` + /// - Parameters: + /// - subscriber: The subscriber to attach to this `Publisher`. + /// once attached it can begin to receive values. + func receive(subscriber: S) where S : Subscriber, S.Failure == UIControlPublisher.Failure, S.Input == UIControlPublisher.Output { + subscriber.receive(subscription: UIControlSubscription(subscriber: subscriber, control: control, event: controlEvents)) + } +} + +/// Extending the `UIControl` types to be able to produce a `UIControl.Event` publisher. +protocol CombineCompatible { } +extension UIControl: CombineCompatible { } +extension CombineCompatible where Self: UIControl { + func publisher(for events: UIControl.Event) -> UIControlPublisher { + return UIControlPublisher(control: self, events: events) + } +}