Skip to content

Conversation

@scottkicks
Copy link
Contributor

@scottkicks scottkicks commented Dec 16, 2025

📲 What

A new custom UITabBar subclass, FloatingTabBar that will replace our current tab bar UI.

This just adds the new class. The next PR will implement it. It will be feature flagged.

SPIKE for reference

🤔 Why

We want a fancy new floating tab bar as shown here

🛠 How

  • Subclasses UITabBar so we can override the UI but still make use of our existing tab navigation logic.
  • Uses a horizontal UIStackView to lay out the tabs, keeping the spacing logic as simple as possible.
  • Animates a green selection background view behind the selected tab.

👀 See

See the POC gifs in the SPIKE

Simulator Screen Recording - iPhone 16 Pro 18 6 - 2025-12-03 at 12 38 22

✅ Acceptance criteria

verified in my POC. this PR doesn't wire up this new class yet

  • Tab bar is centered and floating
  • Selected tab background animates correctly
  • Verivied on simulator and physical device
  • No regressions in dark mode

@scottkicks scottkicks marked this pull request as ready for review December 16, 2025 17:46
@scottkicks scottkicks force-pushed the scott/mbl-2913/floating-tab-bar branch from aae4218 to 9b094a0 Compare December 18, 2025 16:15
@scottkicks scottkicks self-assigned this Dec 18, 2025
@scottkicks scottkicks requested review from a team and amy-at-kickstarter and removed request for a team December 18, 2025 16:26
Copy link
Contributor

@amy-at-kickstarter amy-at-kickstarter left a comment

Choose a reason for hiding this comment

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

A couple blocking changes:

  1. Use KDS spacing constants
  2. Removed the synchronized folder
  3. (If possible) don't change the view hierarchy of private views in UITabBar, keep the code just to layout and design tweaks

Also, can you throw a gif or screenshot in this review? Just for posterity.

static let tabBarShadowOpacity: Float = 0.28
static let tabBarShadowRadius: CGFloat = 28
static let tabBarShadowOffsetY: CGFloat = 8
static let tabBarVerticalPadding: CGFloat = 8
Copy link
Contributor

Choose a reason for hiding this comment

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

These should use KDS spacing constants. Are these designs from the Figma? You should be able to map them 1:1 to Spacing.unit_0x

}

required init?(coder: NSCoder) {
super.init(coder: coder)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This should be a fatalError since nobody should be using this initializer.

Suggested change
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")

self.selectedTabBackgroundView.layer.cornerRadius = Constants.selectedTabBackgroundCornerRadius
self.selectedTabBackgroundView.clipsToBounds = true

addSubview(self.tabBarBackgroundView)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: should call self.addSubview and self.sendSubviewToBack, just for style consistency. I'm surprised swiftformat didn't pick this up 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔

self.selectedTabBackgroundView.isHidden = isEmpty
self.tabsStackView.isHidden = isEmpty

guard isEmpty == false else { return }
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: instead of isEmpty and tabViews[0].center.y, you could call this like

Suggested change
guard isEmpty == false else { return }
guard let firstTab == self.tabViews.first else { return }
let iconsCenterY = firstTab.center.y

dependencies = (
A76E0A4C1D00C00500EC525A /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
Copy link
Contributor

Choose a reason for hiding this comment

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

Alas, we do not supported synchronized folders: see https://kickstarter.atlassian.net/browse/MBL-2676. This will break the build on CI.


/// Make sure the tab StackView items are in the correct order.
/// UITabBar manages these views, so we re-sync them during layout.
private func syncStackViewArrangedSubviews(_ tabViews: [UIView]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify what this is doing? If UITabBar is already managing these views, why are we adding/removing them from the view hierarchy? I'd expect us to just shuffle around their layout, instead.

My concern would be that mucking with the view hierarchy leads to weird bugs down the line.

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 is so that we can fully control the tab layout (floating UI, fixed width, equal spacing, etc.). UITabBar creates and manages the item views, but doesn’t give us a way to lay them out inside a custom container.

To avoid touching the view hierarchy entirely, I think we'd need to build a custom tab bar rather than subclass it.

}
}

/// Returns the view for the given tab item`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: typo

Suggested change
/// Returns the view for the given tab item`.
/// Returns the view for the given tab item.

@kickstarter kickstarter deleted a comment from nativeksr Dec 23, 2025
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.

3 participants