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

[swift] [rfc] Add TabBarScreen Sample #1098

Closed
wants to merge 3 commits into from
Closed

Conversation

JustinDSN
Copy link
Contributor

@JustinDSN JustinDSN commented Apr 19, 2020

  • Adds a TabBarScreen sample with snapshot tests

  • Demonstrates a way to separate BarItem renderings as a separate Workflow to isolate contained tab screen from bar badge value fetch / calculations.

  • Note: Compose Screen concept is out of scope for this PR.

TODO & Open Questions:

  • Should we hang on to only the currently displayed rendering (current implementation) or should we accept an array of AnyScreens and use selected index to control display?
    • Either implementation is technically correct. If we hang on to each screen, then the parent workflow would have to continue to render those workflows each render pass (just like the BackStackScreen).

Single Screen vs. Multiple Screens:
Pros for Single Screen:

  • You don't render screens early (eg: tabs that haven't been visited yet)

Cons for Single Screen:

  • View state could be would between renderings (eg: Scroll Position)
  • How do we want to demonstrate fetching / rendering the badge value for the Baz tab? A separate Workflow or a simple Worker that is awaited in DemoWorkflow?

    • I'm leaning towards a Worker as its simpler and easier to grok, however I do like demonstrating the map rendering here. WDYT?
  • Determine the right height for the tab bar on all the devices, should this come in on the screen or is it an implementation detail?

    • Also focus more in on the safe area insets thing.
  • Should we snip the ContainerView?

  • Any way to tighten up the API (I assume less verbosity)?

@JustinDSN JustinDSN added proposal Issue or PR that proposes something and invites comment on whether or not it's a good idea. swift Affects the Swift library. labels Apr 19, 2020
@JustinDSN JustinDSN added this to the v1.0.0 milestone Apr 19, 2020
view.addSubview(tabBar)

addChild(contentViewController)
containerView.contentView.addSubview(contentViewController.view)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why containerView instead of adding the contentViewController’s view directly?

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 received feedback on my SplitScreen PR from @AquaGeek to use a container view to codify a view who sets it's subviews bounds to its frame on every layout pass. Decided to keep the pattern the same.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can’t find that discussion from that PR. But I feel like this adds an unnecessary layer to the code.

Copy link
Contributor Author

@JustinDSN JustinDSN Apr 21, 2020

Choose a reason for hiding this comment

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

Darn it was in person: #925 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

For context, IIRC that discussion was around adding static container subviews in the split screen so that you don't have to redo all the sizing math when swapping out contents — you just hand the new content view to the appropriate container and it adds it as a subview and does a view.frame = bounds.

internal override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

let tabBarHeight: CGFloat = 83
Copy link
Collaborator

@bencochran bencochran Apr 21, 2020

Choose a reason for hiding this comment

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

Can we ask the tab bar its height? And how does this work across iPhone X-ish sized phones with bottom safe area insets and iPhone 8-ish ones without them?

FBSnapshotVerifyView(
viewController.view,
identifier: name,
suffixes: ["_64"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

What’s the _64for?


extension FooWorkflow {

enum Action: WorkflowAction {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need this action?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope removing both in Bar and Foo.

swift/Samples/TabBarContainer/DemoApp/DemoWorkflow.swift Outdated Show resolved Hide resolved
swift/Samples/TabBarContainer/DemoApp/Bar/BarScreen.swift Outdated Show resolved Hide resolved
swift/Samples/TabBarContainer/Sources/TabBarScreen.swift Outdated Show resolved Hide resolved
@JustinDSN JustinDSN force-pushed the jsteffen/TabBarScreen branch 2 times, most recently from 7b1c413 to 1e74402 Compare April 22, 2020 02:00
override internal func viewDidLoad() {
super.viewDidLoad()

view.addSubview(containerView)
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 shouldn't use UITabBarController here?

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've seen two internal implementations that use BluePrint to render the tab bar and only accept the currently displayed screen.

This allows greater control of what the UI rendering / view behavior looks like. For this sample, I didn't want to use Blueprint, so decided to use a simple UITabBar.

The question of holding onto an array of multiple screens is a good one and should be discussed in more detail. I'd like a few more folks to weigh their opinions on this PR before we circle up as a group to decide.

@JustinDSN JustinDSN force-pushed the jsteffen/TabBarScreen branch 2 times, most recently from 7ca7ed8 to 5a12fc5 Compare April 22, 2020 17:10

extension BazBadgeWorkflow {

struct BarBadgeWorker: Worker {
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 intentionally called Bar instead of Baz?

These names are confusing. I don't have better suggestions outside of building more "real" UI.

}

return TabBarContainerScreen(
screens: [bazScreen, fooScreen],
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 very much of the mind that all the tabs should be rendered in each pass.

extension BarItem {
public enum Badge {
case none
case value(Int)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a separate case for this? Why not force consumers to do the text conversion?

@AquaGeek
Copy link
Contributor

How do we want to demonstrate fetching / rendering the badge value for the Baz tab? A separate Workflow or a simple Worker that is awaited in DemoWorkflow?

Can we use a map function to convert the Output of the hosted workflows to a badge value? e.g.:

  • MessageListWorkflow has an output of "unread messages," which we map to a badge value when rendering it into a tab screen.
  • SettingsWorkflow has an output of "available updates," which we also map to a badge value.

This way, if we reparent these workflows into a different container (e.g. hamburger menu side bar) we don't have to change their content at all.

@bencochran
Copy link
Collaborator

I’d say badge value should be part of rendering of children, not outputs (i.e. it’s their state, not events).

public struct TabScreen {
public let barItem: BarItem
public let content: AnyScreen
public let onSelect: (() -> ())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Snip this, and handle the selection at the parent.

// MARK: Rendering

extension BazWorkflow {
typealias Rendering = TabScreen
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be just BazScreen


typealias State = Void
enum Output {
case tabSelected
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Create an worker output for the badgeCountUpdate.

@JustinDSN
Copy link
Contributor Author

JustinDSN commented May 21, 2020

I’d say badge value should be part of rendering of children, not outputs (i.e. it’s their state, not events).

@bencochran could you clarify this a little more. Do you mean that you'd rather see the rendering be a tuple of screen and badge count? Instead of using the child workflow's output when the badge count is updated?

@JustinDSN
Copy link
Contributor Author

Closing for now - will re-open post repo move and when I have time to wrap up.

@JustinDSN JustinDSN closed this Jun 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal Issue or PR that proposes something and invites comment on whether or not it's a good idea. swift Affects the Swift library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants