Skip to content

Commit

Permalink
Merge pull request #16 from mansbernhardt/release-1.1
Browse files Browse the repository at this point in the history
Release 1.1
  • Loading branch information
Måns Bernhardt authored May 14, 2018
2 parents 7b46286 + d5445e7 commit ac88a3e
Show file tree
Hide file tree
Showing 17 changed files with 681 additions and 150 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# 1.0

This is the first public release of the Flow library.

# 1.1

- Added `DisposeBag.hold()` convenience method for holding a reference to an object.
- Added `UITextField` delegates for `shouldEndEditing` and `shouldReturn`l
- Added `UITextField.isEditingSignal` signal.
- Added `UIView.install()` for installing gesture recognizers.
- Added `UIView` signals for displaying editing menu for copy, cut and paste.
- Added `orientationSignal` that will signal on orientation changes.
- Added `UIRefreshControl` `animate()` and `refersh()` helpers.
- Added `disableActiveEventListeners()` helper
20 changes: 18 additions & 2 deletions Flow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
F64E975F201888EB00865380 /* Future+Combiners.swift in Sources */ = {isa = PBXBuildFile; fileRef = F64E975E201888EB00865380 /* Future+Combiners.swift */; };
F662C0A71FDFDEB300E5F869 /* Signal+Scheduling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F662C0A61FDFDEB300E5F869 /* Signal+Scheduling.swift */; };
F667FCD8200604570014DA7D /* Enablable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F667FCD7200604570014DA7D /* Enablable.swift */; };
F66835CF2091B887002D2676 /* UIView+EditingMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F66835CE2091B887002D2676 /* UIView+EditingMenu.swift */; };
F66835D42091C29C002D2676 /* UIViewSignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F66835D32091C29C002D2676 /* UIViewSignalTests.swift */; };
F66C47A720077B2500333410 /* Signal+Combiners.swift in Sources */ = {isa = PBXBuildFile; fileRef = F66C47A620077B2500333410 /* Signal+Combiners.swift */; };
F66C47A920077BC700333410 /* Signal+Listeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = F66C47A820077BC700333410 /* Signal+Listeners.swift */; };
F6714C711F25E97600C96931 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = F610ABA61D91743500A161AB /* Future.swift */; };
Expand All @@ -50,9 +52,11 @@
F6B6A6652056B2CA00B9FC9D /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B6A6642056B2CA00B9FC9D /* EventType.swift */; };
F6C0FED2202B44360076B877 /* DelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6C0FED1202B44360076B877 /* DelegateTests.swift */; };
F6D80B641BBBB2ED008F8574 /* Flow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6D80B591BBBB2ED008F8574 /* Flow.framework */; };
F6E752312099B60A0092EDAA /* HasEventListeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E752302099B60A0092EDAA /* HasEventListeners.swift */; };
F6EDC6E22066BD39007AC39B /* LifetimeManagement.md in Resources */ = {isa = PBXBuildFile; fileRef = F6EDC6DF2066BD39007AC39B /* LifetimeManagement.md */; };
F6EDC6E32066BD39007AC39B /* Signals.md in Resources */ = {isa = PBXBuildFile; fileRef = F6EDC6E02066BD39007AC39B /* Signals.md */; };
F6EDC6E42066BD39007AC39B /* Futures.md in Resources */ = {isa = PBXBuildFile; fileRef = F6EDC6E12066BD39007AC39B /* Futures.md */; };
F6F679A320A966D1004C7AA7 /* EventListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F679A220A966D1004C7AA7 /* EventListenerTests.swift */; };
F6FF03DE1D926AC300B93771 /* Callbacker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6FF03D41D926AC300B93771 /* Callbacker.swift */; };
F6FF03DF1D926AC300B93771 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6FF03D51D926AC300B93771 /* Disposable.swift */; };
F6FF03E21D926AC300B93771 /* Signal+Transforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6FF03D81D926AC300B93771 /* Signal+Transforms.swift */; };
Expand Down Expand Up @@ -96,6 +100,8 @@
F64E975E201888EB00865380 /* Future+Combiners.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Future+Combiners.swift"; path = "Flow/Future+Combiners.swift"; sourceTree = SOURCE_ROOT; };
F662C0A61FDFDEB300E5F869 /* Signal+Scheduling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Signal+Scheduling.swift"; path = "Flow/Signal+Scheduling.swift"; sourceTree = "<group>"; };
F667FCD7200604570014DA7D /* Enablable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Enablable.swift; path = Flow/Enablable.swift; sourceTree = "<group>"; };
F66835CE2091B887002D2676 /* UIView+EditingMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIView+EditingMenu.swift"; path = "Flow/UIView+EditingMenu.swift"; sourceTree = "<group>"; };
F66835D32091C29C002D2676 /* UIViewSignalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewSignalTests.swift; path = FlowTests/UIViewSignalTests.swift; sourceTree = "<group>"; };
F66C47A620077B2500333410 /* Signal+Combiners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Signal+Combiners.swift"; path = "Flow/Signal+Combiners.swift"; sourceTree = "<group>"; };
F66C47A820077BC700333410 /* Signal+Listeners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Signal+Listeners.swift"; path = "Flow/Signal+Listeners.swift"; sourceTree = "<group>"; };
F67C4797206CDDCC00BEBDFD /* FiniteSignal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FiniteSignal.swift; path = Flow/FiniteSignal.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -124,9 +130,11 @@
F6C2E60E1C6A0E2C00453548 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
F6D80B591BBBB2ED008F8574 /* Flow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Flow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F6D80B631BBBB2ED008F8574 /* FlowTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F6E752302099B60A0092EDAA /* HasEventListeners.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HasEventListeners.swift; path = Flow/HasEventListeners.swift; sourceTree = "<group>"; };
F6EDC6DF2066BD39007AC39B /* LifetimeManagement.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LifetimeManagement.md; sourceTree = "<group>"; };
F6EDC6E02066BD39007AC39B /* Signals.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Signals.md; sourceTree = "<group>"; };
F6EDC6E12066BD39007AC39B /* Futures.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Futures.md; sourceTree = "<group>"; };
F6F679A220A966D1004C7AA7 /* EventListenerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EventListenerTests.swift; path = FlowTests/EventListenerTests.swift; sourceTree = "<group>"; };
F6FF03D41D926AC300B93771 /* Callbacker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Callbacker.swift; path = Flow/Callbacker.swift; sourceTree = "<group>"; };
F6FF03D51D926AC300B93771 /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Disposable.swift; path = Flow/Disposable.swift; sourceTree = "<group>"; };
F6FF03D81D926AC300B93771 /* Signal+Transforms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Signal+Transforms.swift"; path = "Flow/Signal+Transforms.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,10 +210,12 @@
F6AD20551E2FB4F70082CF27 /* Signal+UI */ = {
isa = PBXGroup;
children = (
F667FCD7200604570014DA7D /* Enablable.swift */,
F6FF03DB1D926AC300B93771 /* TargetActionable.swift */,
F6AD20561E2FB5320082CF27 /* UIControls+Extensions.swift */,
F68EF3541FD58FD20001129C /* UIView+Signal.swift */,
F66835CE2091B887002D2676 /* UIView+EditingMenu.swift */,
F667FCD7200604570014DA7D /* Enablable.swift */,
F6FF03DB1D926AC300B93771 /* TargetActionable.swift */,
F6E752302099B60A0092EDAA /* HasEventListeners.swift */,
);
name = "Signal+UI";
sourceTree = "<group>";
Expand Down Expand Up @@ -272,6 +282,8 @@
F610ABBB1D91747000A161AB /* MultipleContinuationsTests.swift */,
215DEF351DEC367E00CEB724 /* RecursiveTests.swift */,
F6C0FED1202B44360076B877 /* DelegateTests.swift */,
F66835D32091C29C002D2676 /* UIViewSignalTests.swift */,
F6F679A220A966D1004C7AA7 /* EventListenerTests.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -406,6 +418,7 @@
F610ABB01D91743500A161AB /* FutureQueue.swift in Sources */,
21E1D41C1D9502A300A91CA0 /* Future+Signal.swift in Sources */,
215DEF311DEC365900CEB724 /* Recursive.swift in Sources */,
F6E752312099B60A0092EDAA /* HasEventListeners.swift in Sources */,
F610ABAE1D91743500A161AB /* Future+Additions.swift in Sources */,
F68EF3571FD590110001129C /* SignalProvider.swift in Sources */,
F6B57E891E30B01700703CA7 /* OrderedCallbacker.swift in Sources */,
Expand All @@ -432,6 +445,7 @@
F66C47A920077BC700333410 /* Signal+Listeners.swift in Sources */,
F64E975F201888EB00865380 /* Future+Combiners.swift in Sources */,
F6714C711F25E97600C96931 /* Future.swift in Sources */,
F66835CF2091B887002D2676 /* UIView+EditingMenu.swift in Sources */,
F6FF03E51D926AC300B93771 /* TargetActionable.swift in Sources */,
F699C483205C1A5C001378C0 /* Signal+Utilities.swift in Sources */,
F667FCD8200604570014DA7D /* Enablable.swift in Sources */,
Expand All @@ -455,7 +469,9 @@
F610ABBE1D91747000A161AB /* FutureRepeatTests.swift in Sources */,
F610ABC01D91747000A161AB /* FutureSplitTests.swift in Sources */,
215DEF371DEC368700CEB724 /* RecursiveTests.swift in Sources */,
F66835D42091C29C002D2676 /* UIViewSignalTests.swift in Sources */,
F6C0FED2202B44360076B877 /* DelegateTests.swift in Sources */,
F6F679A320A966D1004C7AA7 /* EventListenerTests.swift in Sources */,
F610ABBD1D91747000A161AB /* FutureQueueTests.swift in Sources */,
F610ABC11D91747000A161AB /* FutureUtilitiesTests.swift in Sources */,
);
Expand Down
9 changes: 8 additions & 1 deletion Flow/Disposable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,18 @@ public final class DisposeBag: Disposable {

public extension DisposeBag {
/// Creates a new bag, adds it to self, and returns it
public func innerBag() -> DisposeBag {
func innerBag() -> DisposeBag {
let bag = DisposeBag()
add(bag)
return bag
}

/// Will hold a reference to `object` until self is disposed.
///
/// bag.hold(delegate)
func hold(_ object: AnyObject...) {
self += { _ = object }
}
}

public func +=(disposeBag: DisposeBag, disposable: Disposable?) {
Expand Down
6 changes: 0 additions & 6 deletions Flow/Enablable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ public extension SignalProvider where Kind == ReadWrite, Value == Bool {
}
}

/// Whether the conforming class has event listeners.
public protocol HasEventListeners: class {
/// Boolean value indicating whether the instance has event listeners.
var hasEventListeners: Bool { get }
}

/// Whether the conforming object supports auto enabling.
public protocol AutoEnablable: Enablable {
/// Boolean value indicating whether the instance should be automatically enabled while having listeners
Expand Down
82 changes: 82 additions & 0 deletions Flow/HasEventListeners.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// HasEventListeners.swift
// Flow
//
// Created by Måns Bernhardt on 2015-11-27.
// Copyright © 2015 iZettle. All rights reserved.
//

import Foundation


/// Whether the conforming class has event listeners.
public protocol HasEventListeners: class {
/// Boolean value indicating whether the instance currently has event listeners.
var hasEventListeners: Bool { get }
}

/// Whether the conforming type can provide an array of all sub views/items that conform to `Enablable & HasEventListeners`.
public protocol HasEnablableEventListeners {
// An array of all sub views/items that conform to `Enablable & HasEventListeners`
var enablableEventListeners: [Enablable & HasEventListeners] { get }
}

public extension HasEnablableEventListeners {
/// Will find and disable all sub views/items that conform `Enablable & HasEventListeners` that currently has event listeners.
/// - Returns: A `Disposable` that will upon dispose re-enable the views/items being disabled.
func disableActiveEventListeners() -> Disposable {
let activeListeners = enablableEventListeners.filter { $0.hasEventListeners }.filter { ($0 as Enablable).isEnabled }
activeListeners.forEach { $0.isEnabled = false }
return Disposer {
activeListeners.filter { $0.hasEventListeners }.forEach { $0.isEnabled = true }
}
}
}

#if canImport(UIKit)

import UIKit

extension UIView: HasEnablableEventListeners {
public var enablableEventListeners: [Enablable & HasEventListeners] {
return allSubviews(ofType: (Enablable & HasEventListeners).self)
}
}

extension UINavigationItem: HasEnablableEventListeners {
public var enablableEventListeners: [Enablable & HasEventListeners] {
return allItemsOrViews(ofType: (Enablable & HasEventListeners).self)
}
}

extension UIViewController: HasEnablableEventListeners {
public var enablableEventListeners: [Enablable & HasEventListeners] {
return view.enablableEventListeners + navigationItem.enablableEventListeners
}
}

private extension UINavigationItem {
func allItemsOrViews<T>(ofType type: T.Type) -> [T] {
var result = [T]()
let items = (leftBarButtonItems ?? []) + (rightBarButtonItems ?? [])
result += items.compactMap { $0 as? T }
result += items.compactMap { $0.customView }.flatMap { $0.allSubviews(ofType: T.self) }
return result
}
}

internal extension UIView {
var allSubviews: [UIView] {
return subviews + subviews.flatMap {
$0.allSubviews
}
}

func allSubviews<T>(ofType type: T.Type) -> [T] {
return allSubviews.compactMap { $0 as? T }
}
}

#endif


6 changes: 3 additions & 3 deletions Flow/Signal+Listeners.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ public extension SignalProvider where Kind == ReadWrite, Value: Equatable {
/// Start listening on values for both `self` and `signal` and update each other's value with the latest signaled value.
/// - Returns: A disposable that will stop listening on values when being disposed.
/// - Note: Infinite recursion is avoided by comparing equality with the previous value.
func bidirectionallyBindTo<P: SignalProvider>(_ property: P) -> Disposable where P.Value == Value, P.Kind == ReadWrite {
return bidirectionallyBindTo(property, isSame: ==)
func bidirectionallyBindTo<ReadWriteSignal: SignalProvider>(_ signal: ReadWriteSignal) -> Disposable where ReadWriteSignal.Value == Value, ReadWriteSignal.Kind == ReadWrite {
return bidirectionallyBindTo(signal, isSame: ==)
}
}

public extension SignalProvider where Value == () {
/// Start listening on values and toggle `signal`'s value for every recieved event.
/// - Returns: A disposable that will stop listening on values when being disposed.
func toggle<P: SignalProvider>(_ signal: P) -> Disposable where P.Value == Bool, P.Kind == ReadWrite {
func toggle<ReadWriteSignal: SignalProvider>(_ signal: ReadWriteSignal) -> Disposable where ReadWriteSignal.Value == Bool, ReadWriteSignal.Kind == ReadWrite {
let signal = signal.providedSignal
return onValue { signal.value = !signal.value }
}
Expand Down
2 changes: 1 addition & 1 deletion Flow/Signal+Scheduling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public extension CoreSignal where Kind == Plain, Value == () {

let timer = DispatchSource.makeTimerSource(queue: .concurrentBackground)

bag += { _ = timer } // DispatchSourceTimer is automatically cancelled after being released
bag.hold(timer) // DispatchSourceTimer is automatically cancelled after being released
timer.setEventHandler {
c(())
}
Expand Down
4 changes: 2 additions & 2 deletions Flow/TargetActionable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public extension SignalProvider where Self: TargetActionable {
action = TargetAction.selector
}

(self as? AutoEnablable&HasEventListeners)?.updateAutomaticEnabling()
(self as? AutoEnablable & HasEventListeners)?.updateAutomaticEnabling()

return Signal { callback in
let d = targetAction.addCallback(callback)
Expand All @@ -39,7 +39,7 @@ public extension SignalProvider where Self: TargetActionable {
if targetAction.callbacker.isEmpty {
self.target = nil
self.action = nil
(self as? AutoEnablable&HasEventListeners)?.updateAutomaticEnabling()
(self as? AutoEnablable & HasEventListeners)?.updateAutomaticEnabling()
}
}
}.readable()
Expand Down
Loading

0 comments on commit ac88a3e

Please sign in to comment.