Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split VC isCollapsed fix (issue 54) #56

Merged
merged 9 commits into from
Sep 24, 2019
Merged

Conversation

nataliq
Copy link
Contributor

@nataliq nataliq commented Sep 20, 2019

1.9.0

- [Addition] Add a new `isCollapsedState` signal to DualNavigationControllersSplitDelegate that has a `nil` value until the collapsed state is known. Old `isCollapsedSignal` is deprecated.
- [Addition] Add a new `init(collapsedState:)` method to DualNavigationControllersSplitDelegate that takes a future to get notified of a known collapsed state. The `init` without parameters is deprecated.
- [Change] Deprecate MasterDetailSelection's init with `isCollapsed` signal in favour of init that can handle a `nil` collapsed state
- [Bug fix] DualNavigationControllersSplitDelegate's `isCollapsedSignal` didn't signal `false` when moving from collapsed state to not collapsed (multitasking/rotation)
- [Bug fix] DualNavigationControllersSplitDelegate's `isCollapsedSignal` didn't signal anything on iOS 13 ([issue #54](https://github.com/iZettle/Presentation/issues/54))

Tested with the Messages app on iOS 11 and 13. Tested in the main iZettle project too.

@nataliq nataliq requested a review from enhorn September 23, 2019 08:20
enhorn
enhorn previously approved these changes Sep 23, 2019
@nataliq nataliq requested a review from a team as a code owner September 23, 2019 09:13
@nataliq
Copy link
Contributor Author

nataliq commented Sep 23, 2019

@enhorn can you take a look again? I'm very happy to not use the new name collapsedState if you have ideas how to maintain the previous API so that this is not a breaking change. Or maybe introduce an enum that has unknown/collapsed/expanded states.

@nataliq
Copy link
Contributor Author

nataliq commented Sep 23, 2019

@mansbernhardt thought you might have some input on this PR?

@enhorn
Copy link
Contributor

enhorn commented Sep 23, 2019

I like the idea of the enum instead of an optional boolean. Opens for clearer documentation as well.

}

/// Returns a signal that will signal when collapsing or expanding. Current value can be nil the collapsed state cannot be determined reliably yet.
public var collapsedState: ReadSignal<Bool?> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the Bool? as it states nicely what is going on (true, false or unknown), hence I doubt an enum will make things clearer (perhaps even the opposite). However, we could consider just naming this isCollapsed (as it is not taken and it is not that uncommon that we name read or readwrite signals without the signal postfix, especially when we don't have a name collision). If however you like to stay with the state variant, I would prefer isCollapsedState instead.

public var isCollapsedSignal: ReadSignal<Bool> {
return isCollapsedProperty.readOnly().map { $0 ?? true }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a readonly as map will make it readable anyway.

/// Note that its value can be unreliable until it is rendered on the screen.
public init(collapsedState: Future<Bool>) {
super.init()
initialCollapsedStateDisposable += collapsedState.onValue { [weak self] in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is initialCollapsedStateDisposable necessary, as we hold self weakly anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use the bag only to remove the subscription earlier if one of the other 2 delegate methods setting the state get triggered before the future completes. However that might not be very realistic case, what do you think?

/// Creates a delegate that will manage a split view controller with the specified initial collapsed state
/// - Parameter collapsedState: Use to report the initial `isCollapsed` state of the split view controller.
/// Note that its value can be unreliable until it is rendered on the screen.
public init(collapsedState: Future<Bool>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we pass this as a future instead of as a signal? Sound more like something that might change over time than something that takes time to complete? Rename to isCollapsed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source of truth for the collapsed state is the split view controller. I think its isCollapsed property is not kvo compliant. It seems like once it's on screen the delegate methods track properly the collapsing/expanding so I modelled it as a future to make it clear it's only the first time it gets added to a window that we don't know what's the state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree for the renaming.


/// Creates a new instance using changes in `elements` and `isCollapsed` to maintain the selected detail index (provided signal).
/// - Parameters:
/// - isSame: Is it the same row (same identity)
/// - needsUpdate: For the same row, does the row have updates that requires presenting new details (refresh details)
/// - isCollapsed: Whether or not details are displayed.
public init(elements: ReadSignal<Elements>, isSame: @escaping (Element, Element) -> Bool, needsUpdate: @escaping (Element, Element) -> Bool = { _, _ in false }, isCollapsed: ReadSignal<Bool>) {
public init(elements: ReadSignal<Elements>, isSame: @escaping (Element, Element) -> Bool, needsUpdate: @escaping (Element, Element) -> Bool = { _, _ in false }, collapsedState: ReadSignal<Bool?>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can keep the same parameter name isCollapsed, but just change the type and let both the deprecated and the new init live side by side only differ by the signal name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it's working 👍


bag += isCollapsed.atOnce().onValueDisposePrevious { [weak self] isCollapsed in
guard let `self` = self else { return NilDisposer() }
bag += collapsedState.atOnce().onValueDisposePrevious { [weak self] isCollapsed in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use with(weak: self) here?

bag += isCollapsed.atOnce().onValueDisposePrevious { [weak self] isCollapsed in
guard let `self` = self else { return NilDisposer() }
bag += collapsedState.atOnce().onValueDisposePrevious { [weak self] isCollapsed in
guard let `self` = self, let isCollapsed = isCollapsed else { return NilDisposer() }
return self.keepSelection.atOnce().enumerate().onValue { [weak self] (eventCount, indexAndElement) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use with(weak: self) here?

@@ -116,7 +121,7 @@ public final class MasterDetailSelection<Elements: BidirectionalCollection>: Sig
guard let `self` = self else {
return NilDisposer()
}
onSet(self.isCollapsed.value ? nil : self.current)
onSet((self.isCollapsed.value == nil || self.isCollapsed.value == true) ? nil: self.current)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onSet((self.isCollapsed.value ?? true) ? nil: self.current)

@@ -156,7 +162,7 @@ public extension MasterDetailSelection {

immediate = true
let presentDisposable = vc.present(presentation.onDismiss {
if self.isCollapsed.value && !immediate {
if isCollapsed && !immediate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this changing the behavior as you using an old value in an async op?
If not perhaps isCollapsed should be name wasCollapsed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch, I missed the fact that this block of code is async. I'm struggling to decide what to do in the nil case here because I think that's not gonna happen (see a bit above we have an early return when isCollapsed is nil and it will never change from true/false to nil). I'm just gonna return early in that case. Btw this is the behaviour that this code influences and it was broken with the previous commit!
splitvc-deselect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an assertion failure since that will notify us if this behaviour is not as we expect it in debug builds.

@nataliq
Copy link
Contributor Author

nataliq commented Sep 23, 2019

@niil-ohlin I'm getting some strange swiftlint failure on the CI for unused self now that I'm using .with(weak: self). When I run swiftlint --strict locally I get no warnings. Where can I see what version of SwiftLint we use?

@nataliq
Copy link
Contributor Author

nataliq commented Sep 24, 2019

I think Swiftlint should be happy now. Can you re-review?

@nataliq nataliq merged commit 8e38f9c into master Sep 24, 2019
@nataliq nataliq deleted the split-vc-iscollapsed-fix branch September 24, 2019 12:20
@nataliq
Copy link
Contributor Author

nataliq commented Sep 24, 2019

Pressed merge instead of squash.. oh well 😔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants