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

Add ViewEnvironment (#739) #999

Merged
merged 2 commits into from
Mar 5, 2020
Merged

Add ViewEnvironment (#739) #999

merged 2 commits into from
Mar 5, 2020

Conversation

bencochran
Copy link
Collaborator

(ViewEnvironment is the Swift implementation of Kotlin’s ContainerHints. I included the straw-man rename of them to ViewEnvironment.)

ViewEnvironment is a container of strongly-typed key/value pairs that can be passed down the view-side of a workflow tree. This allows containers to provide view-oriented hinting to their children as well as allows for view-side changes to view-only environment without re-rendering the workflow tree (for example, runtime theme changes).


/// Returns a new screen that will render the contents of this screen, with
/// the given environment key path set to the given value.
public func withEnvironment<Value>(_ keyPath: WritableKeyPath<ViewEnvironment, Value>, value: Value) -> EnvironmentScreen<Value, Self> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This extension is getting a little SwiftUI-y. Thoughts?

Copy link
Contributor

@dhavalshreyas dhavalshreyas Mar 4, 2020

Choose a reason for hiding this comment

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

Can we make it more SwiftUI-y?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For the full effect I could spell it this way:

public func environment<Value>(_ keyPath: WritableKeyPath<ViewEnvironment, Value>, _ value: Value) -> EnvironmentScreen<Value, Self>

Copy link
Collaborator Author

@bencochran bencochran Mar 4, 2020

Choose a reason for hiding this comment

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

I tend to shy away from unnamed parameters, but in the case of this one I think it’s actually alright?

ex:

someScreen
    .environment(\.theme, DarkTheme())
    .environment(\.padding, 24)

Copy link
Contributor

Choose a reason for hiding this comment

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

I like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like that too!

}

private enum SplitScreenPositionKey: ViewEnvironmentKey {
static var defaultValue: SplitScreenPosition = .none
Copy link
Contributor

Choose a reason for hiding this comment

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

This is cool! I'm kinda blown away that a private type's internal static var is accessible from outside this file.

Copy link
Contributor

Choose a reason for hiding this comment

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

SplitScreenPositionKey is not accessed from outside this file.

Copy link
Contributor

@JustinDSN JustinDSN Mar 4, 2020

Choose a reason for hiding this comment

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

But it's defaultValue var is.



/// A wrapper screen that overrides environment values
public struct EnvironmentScreen<Value, Content> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there value in this? Without this ViewEnvironment is fully contained within viewControllerDescription and UIViewControllers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ViewControllerDescription itself doesn’t have any knowledge of ViewEnvironment.

My desire for this is for wrapper workflows to be able to provide environment values without having to implement a wrapper screen themselves. Let’s use automatic day/night theme switching as an example (ignoring iOS’s dark mode support for a moment). I could have a workflow that has itself subscribes to some signal for time of day, changing its state to .day or .night as appropriate. With this extension, it’s render could be:

func render(state: State, context: RenderContext<AutoNightModeWorkflow>) -> Rendering {
    return wrappedWorkflow
        .mapRendering { $0.environment(\.theme, Theme(style: state.timeOfDay) }
        .rendered(with: context)
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't this break the UI/Workflow decoupling? ViewEnvironment is purely a view concern, so the workflows shouldn't know about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IMO, it’s okay for a workflow to put values into the environment. But not to take them out. We absolutely could make a workflow like the one described above do the same job by implementing a wrapper screen type, but it’s effectively going to do the same thing and be the same flow of information.

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 have this discussion in a separate PR, please? It's a big change from the Kotlin behavior, for a speculative use case. And it's not required by the general machinery here, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure. I’ll kick it out of this PR when I update to fix the tutorial code.

Copy link
Contributor

Choose a reason for hiding this comment

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

While I'm thinking about it, though: one thing to note in defense of this class is that container views themselves can transform child renderings into other screen classes. I've already done that a few times, very handy.

That said, I still don't think this class should be a part of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, thinking more about this - this might be the primary way for us to set environment values.

Copy link
Contributor

Choose a reason for hiding this comment

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

}

set {
storage[ObjectIdentifier(key)] = newValue
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given struct's value semantics, does this actually mutate the current value? In the Kotlin API ContainerHints is entirely immutable, we don't provide any mutating operators.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In a struct this is effectively a mutating func, so the mutability follows our standard value semantics (isn’t shared).

So for example you cannot do:

func update(screen: MyScreen, environment: ViewEnvironment) {
    environment[SomeKey.self] = 14
}

ViewEnvironment is a container of strongly-typed key/value pairs that can be
passed down the view-side of a workflow tree. This allows containers to provide
view-oriented hinting to their children as well as allows for view-side changes
to view-only environment without re-rendering the workflow tree (for example,
runtime theme changes).
@bencochran bencochran force-pushed the bc/view-environment branch from 436627d to 6a46f0e Compare March 4, 2020 23:15
@bencochran
Copy link
Collaborator Author

Removed EnvironmentScreen (into a branch waiting to be PR’d once this lands), fixed broken Tutorials, and updated docs and templates. Let me know if people think this is ready to land.

Copy link
Contributor

@dhavalshreyas dhavalshreyas left a comment

Choose a reason for hiding this comment

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

🚢 🔥

This is an awesome PR and a very neat feature, thank you!

@bencochran bencochran merged commit 9b38efd into master Mar 5, 2020
@bencochran bencochran deleted the bc/view-environment branch March 5, 2020 06:32
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