From 8b6b29878ac770747654a1b87737cf242c6ab4dd Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 17 Apr 2026 16:13:25 -0700 Subject: [PATCH 01/10] Draft of glass effect --- .../Demos/CommandBarDemoController.swift | 32 ++++- .../Components/CommandBar/CommandBar.swift | 109 +++++++++++++++++- .../CommandBar/CommandBarTokenSet.swift | 25 +++- 3 files changed, 155 insertions(+), 11 deletions(-) diff --git a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift index 35da364fb4..10be0f8d64 100644 --- a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift +++ b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift @@ -159,6 +159,7 @@ class CommandBarDemoController: DemoController { var defaultCommandBar: CommandBar? var animateCommandBarDelegateEvents: Bool = false + var isGlassStyle: Bool = true lazy var textField: UITextField = { let textField = UITextField() @@ -180,7 +181,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(createLabelWithText("Default")) - let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]]) + let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]], style: .glass) commandBar.delegate = self commandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(commandBar) @@ -269,13 +270,21 @@ class CommandBarDemoController: DemoController { commandBarDelegateEventAnimationView.addArrangedSubview(commandBarDelegateEventAnimationSwitch) itemCustomizationContainer.addArrangedSubview(commandBarDelegateEventAnimationView) + let glassStyleStackView = createHorizontalStackView() + glassStyleStackView.addArrangedSubview(createLabelWithText("Glass Style")) + let glassStyleSwitch = BrandedSwitch() + glassStyleSwitch.isOn = isGlassStyle + glassStyleSwitch.addTarget(self, action: #selector(glassStyleValueChanged), for: .valueChanged) + glassStyleStackView.addArrangedSubview(glassStyleSwitch) + itemCustomizationContainer.addArrangedSubview(glassStyleStackView) + itemCustomizationContainer.addArrangedSubview(UIView()) //Spacer container.addArrangedSubview(itemCustomizationContainer) container.addArrangedSubview(createLabelWithText("With Fixed Button")) - let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]]) + let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]], style: .glass) fixedButtonCommandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(fixedButtonCommandBar) @@ -293,7 +302,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(textFieldContainer) - let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]]) + let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]],style: .glass) accessoryCommandBar.translatesAutoresizingMaskIntoConstraints = false #if os(iOS) textField.inputAccessoryView = accessoryCommandBar @@ -470,6 +479,23 @@ class CommandBarDemoController: DemoController { animateCommandBarDelegateEvents = sender.isOn } + @objc func glassStyleValueChanged(sender: UISwitch!) { + isGlassStyle = sender.isOn + guard let commandBar = defaultCommandBar, + let stackView = commandBar.superview as? UIStackView, + let index = stackView.arrangedSubviews.firstIndex(of: commandBar) else { + return + } + let newCommandBar = CommandBar(itemGroups: createItemGroups(), + leadingItemGroups: [[newItem(for: .keyboard)]], + style: isGlassStyle ? .glass : .primary) + newCommandBar.delegate = self + newCommandBar.translatesAutoresizingMaskIntoConstraints = false + commandBar.removeFromSuperview() + stackView.insertArrangedSubview(newCommandBar, at: index) + defaultCommandBar = newCommandBar + } + @objc func refreshDefaultBarItems(sender: UIButton!) { defaultCommandBar?.itemGroups = createItemGroups() } diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index df41c96f25..5f24dde96b 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -78,10 +78,21 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { trailingItemGroups: trailingItems) } + @objc public convenience init(itemGroups: [CommandBarItemGroup], + leadingItemGroups: [CommandBarItemGroup]? = nil, + trailingItemGroups: [CommandBarItemGroup]? = nil) { + self.init(itemGroups: itemGroups, + leadingItemGroups: leadingItemGroups, + trailingItemGroups: trailingItemGroups, + style: .primary) + } + @objc public init(itemGroups: [CommandBarItemGroup], leadingItemGroups: [CommandBarItemGroup]? = nil, - trailingItemGroups: [CommandBarItemGroup]? = nil) { - self.tokenSet = CommandBarTokenSet() + trailingItemGroups: [CommandBarItemGroup]? = nil, + style: CommandBarStyle) { + self.style = style + self.tokenSet = CommandBarTokenSet(style: { style }) leadingCommandGroupsView = CommandBarCommandGroupsView(itemGroups: leadingItemGroups, buttonsPersistSelection: false, @@ -104,6 +115,9 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { super.init(frame: .zero) configureHierarchy() + if style == .glass { + setupGlassBackground() + } updateBackgroundColor() updateShadow() @@ -159,6 +173,7 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { updateShadow() updateScrollViewShadow() + updateCornerRadius() } #if DEBUG @@ -191,6 +206,9 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { public typealias TokenSetKeyType = CommandBarTokenSet.Tokens public var tokenSet: CommandBarTokenSet + /// The visual style of the CommandBar. + public let style: CommandBarStyle + /// Items shown in the center of the CommandBar @objc public var itemGroups: [CommandBarItemGroup] { get { @@ -246,6 +264,8 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { // MARK: - Private properties + private var glassEffectView: UIVisualEffectView? + /// Container UIStackView that holds the leading, main and trailing views private var commandBarContainerStackView: UIStackView @@ -424,12 +444,32 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } private func updateShadow() { - let shadowInfo = tokenSet[.shadow].shadowInfo - shadowInfo.applyShadow(to: self) + switch style { + case .primary: + let shadowInfo = tokenSet[.shadow].shadowInfo + shadowInfo.applyShadow(to: self) + case .glass: + layer.shadowColor = UIColor.black.cgColor + layer.shadowOpacity = 0.25 + layer.shadowOffset = CGSize(width: 0, height: -2) + layer.shadowRadius = 8 + } } private func updateBackgroundColor() { - backgroundColor = tokenSet[.backgroundColor].uiColor + switch style { + case .primary: + backgroundColor = tokenSet[.backgroundColor].uiColor + case .glass: + backgroundColor = .clear +#if !os(visionOS) + if #available(iOS 26, *), let effectView = glassEffectView { + let glassEffect = UIGlassEffect(style: .regular) + glassEffect.tintColor = tokenSet[.backgroundColor].uiColor + effectView.effect = glassEffect + } +#endif + } } private func updateButtonTokens() { @@ -438,6 +478,65 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { trailingCommandGroupsView.updateButtonsShown() } + private func setupGlassBackground() { + var effectView: UIVisualEffectView +#if os(visionOS) + effectView = getBlurEffectView() +#else + if #available(iOS 26, *) { + effectView = getGlassEffectView() + } else { + effectView = getBlurEffectView() + } +#endif + effectView.translatesAutoresizingMaskIntoConstraints = false + commandBarContainerStackView.insertSubview(effectView, at: 0) + pinEffectView(effectView, to: commandBarContainerStackView) + commandBarContainerStackView.backgroundColor = .clear + glassEffectView = effectView + } + +#if !os(visionOS) + @available(iOS 26, *) + private func getGlassEffectView() -> UIVisualEffectView { + let effectView = UIVisualEffectView() + let glassEffect = UIGlassEffect(style: .regular) + glassEffect.tintColor = tokenSet[.backgroundColor].uiColor + effectView.effect = glassEffect + return effectView + } +#endif // !os(visionOS) + + private func getBlurEffectView() -> UIVisualEffectView { + let effectView = UIVisualEffectView() + effectView.effect = UIBlurEffect(style: .systemMaterial) + effectView.layer.masksToBounds = true + return effectView + } + + private func updateCornerRadius() { + guard style == .glass, let effectView = glassEffectView else { return } + let cornerRadius = bounds.height / 2 +#if !os(visionOS) + if #available(iOS 26, *) { + effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) + } else { + effectView.layer.cornerRadius = cornerRadius + } +#else + effectView.layer.cornerRadius = cornerRadius +#endif + } + + private func pinEffectView(_ view: UIView, to container: UIView) { + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: container.topAnchor), + view.leadingAnchor.constraint(equalTo: container.leadingAnchor), + view.bottomAnchor.constraint(equalTo: container.bottomAnchor), + view.trailingAnchor.constraint(equalTo: container.trailingAnchor) + ]) + } + /// Updates the provided `CommandBarCommandGroupsView` with the `items` array and marks the view as needing a layout private func setupGroupsView(_ commandGroupsView: CommandBarCommandGroupsView, with items: [CommandBarItemGroup]?) { commandGroupsView.itemGroups = items ?? [] diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift index 34c0ce99b4..192affe8a9 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift @@ -8,6 +8,15 @@ import FluentUI_common #endif import UIKit +@objc(MSFCommandBarStyle) +public enum CommandBarStyle: Int { + /// Default style — solid background color. + case primary + + /// Glass material background (UIGlassEffect on iOS 26+, UIBlurEffect on earlier). + case glass +} + public enum CommandBarToken: Int, TokenSetKey { /// The background color of the Command Bar. case backgroundColor @@ -57,11 +66,19 @@ public enum CommandBarToken: Int, TokenSetKey { /// Design token set for the `CommandBar` control. public class CommandBarTokenSet: ControlTokenSet { - init() { - super.init { token, theme in + init(style: @escaping () -> CommandBarStyle) { + self.style = style + super.init { [style] token, theme in switch token { case .backgroundColor: - return .uiColor { theme.color(.background2) } + return .uiColor { + switch style() { + case .primary: + return theme.color(.background2) + case .glass: + return .clear + } + } case .cornerRadius: return .float { GlobalTokens.corner(.radius120) } @@ -107,6 +124,8 @@ public class CommandBarTokenSet: ControlTokenSet { } } } + + var style: () -> CommandBarStyle } // MARK: - Constants From 3cdaa90797d6e5252620b1928870593ef7427441 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 13:40:32 -0700 Subject: [PATCH 02/10] Code Clean up --- .../Components/CommandBar/CommandBar.swift | 44 +++++++++---------- .../CommandBar/CommandBarTokenSet.swift | 8 ++++ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index 5f24dde96b..d96640acca 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -115,9 +115,11 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { super.init(frame: .zero) configureHierarchy() + if style == .glass { setupGlassBackground() } + updateBackgroundColor() updateShadow() @@ -449,10 +451,10 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { let shadowInfo = tokenSet[.shadow].shadowInfo shadowInfo.applyShadow(to: self) case .glass: - layer.shadowColor = UIColor.black.cgColor - layer.shadowOpacity = 0.25 - layer.shadowOffset = CGSize(width: 0, height: -2) - layer.shadowRadius = 8 + layer.shadowColor = CommandBarTokenSet.glassEffectShadowColor + layer.shadowOpacity = CommandBarTokenSet.glassEffectShadowOpacity + layer.shadowOffset = CommandBarTokenSet.glassEffectShadowOffset + layer.shadowRadius = CommandBarTokenSet.glassEffectShadowRadius } } @@ -491,7 +493,12 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { #endif effectView.translatesAutoresizingMaskIntoConstraints = false commandBarContainerStackView.insertSubview(effectView, at: 0) - pinEffectView(effectView, to: commandBarContainerStackView) + NSLayoutConstraint.activate([ + effectView.topAnchor.constraint(equalTo: commandBarContainerStackView.topAnchor), + effectView.leadingAnchor.constraint(equalTo: commandBarContainerStackView.leadingAnchor), + effectView.bottomAnchor.constraint(equalTo: commandBarContainerStackView.bottomAnchor), + effectView.trailingAnchor.constraint(equalTo: commandBarContainerStackView.trailingAnchor) + ]) commandBarContainerStackView.backgroundColor = .clear glassEffectView = effectView } @@ -515,26 +522,15 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } private func updateCornerRadius() { - guard style == .glass, let effectView = glassEffectView else { return } - let cornerRadius = bounds.height / 2 -#if !os(visionOS) - if #available(iOS 26, *) { - effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) - } else { - effectView.layer.cornerRadius = cornerRadius - } -#else - effectView.layer.cornerRadius = cornerRadius -#endif - } + if style == .glass, let effectView = glassEffectView { + let cornerRadius = bounds.height / 2 - private func pinEffectView(_ view: UIView, to container: UIView) { - NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: container.topAnchor), - view.leadingAnchor.constraint(equalTo: container.leadingAnchor), - view.bottomAnchor.constraint(equalTo: container.bottomAnchor), - view.trailingAnchor.constraint(equalTo: container.trailingAnchor) - ]) + if #available(iOS 26, visionOS 26, *) { + effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) + } else { + effectView.layer.cornerRadius = cornerRadius + } + } } /// Updates the provided `CommandBarCommandGroupsView` with the `items` array and marks the view as needing a layout diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift index 192affe8a9..f4ec2d81b2 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift @@ -128,6 +128,14 @@ public class CommandBarTokenSet: ControlTokenSet { var style: () -> CommandBarStyle } +// MARK: Constants +extension CommandBarTokenSet { + static let glassEffectShadowColor: CGColor = UIColor.black.cgColor + static let glassEffectShadowOpacity: Float = 0.25 + static let glassEffectShadowOffset: CGSize = CGSize(width: 0, height: -2) + static let glassEffectShadowRadius: CGFloat = 8 +} + // MARK: - Constants extension CommandBarTokenSet { From b147073b2ef773af4652b5347c56a686bf5b5181 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 13:48:27 -0700 Subject: [PATCH 03/10] Fixed corner radius calculation --- Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index d96640acca..2e32acdaf4 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -168,7 +168,7 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { public override func layoutSubviews() { super.layoutSubviews() - let cornerRadius = bounds.height / 2 + let cornerRadius = commandBarContainerStackView.bounds.height / 2 layer.cornerRadius = cornerRadius commandBarContainerStackView.layer.cornerRadius = cornerRadius commandBarContainerStackView.layoutIfNeeded() @@ -523,7 +523,7 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { private func updateCornerRadius() { if style == .glass, let effectView = glassEffectView { - let cornerRadius = bounds.height / 2 + let cornerRadius = commandBarContainerStackView.bounds.height / 2 if #available(iOS 26, visionOS 26, *) { effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) From 1c102013b996a028066a9cf8279339e8862bacf7 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 13:57:31 -0700 Subject: [PATCH 04/10] Revert demo controller changes --- .../Demos/CommandBarDemoController.swift | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift index 10be0f8d64..35da364fb4 100644 --- a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift +++ b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift @@ -159,7 +159,6 @@ class CommandBarDemoController: DemoController { var defaultCommandBar: CommandBar? var animateCommandBarDelegateEvents: Bool = false - var isGlassStyle: Bool = true lazy var textField: UITextField = { let textField = UITextField() @@ -181,7 +180,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(createLabelWithText("Default")) - let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]], style: .glass) + let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]]) commandBar.delegate = self commandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(commandBar) @@ -270,21 +269,13 @@ class CommandBarDemoController: DemoController { commandBarDelegateEventAnimationView.addArrangedSubview(commandBarDelegateEventAnimationSwitch) itemCustomizationContainer.addArrangedSubview(commandBarDelegateEventAnimationView) - let glassStyleStackView = createHorizontalStackView() - glassStyleStackView.addArrangedSubview(createLabelWithText("Glass Style")) - let glassStyleSwitch = BrandedSwitch() - glassStyleSwitch.isOn = isGlassStyle - glassStyleSwitch.addTarget(self, action: #selector(glassStyleValueChanged), for: .valueChanged) - glassStyleStackView.addArrangedSubview(glassStyleSwitch) - itemCustomizationContainer.addArrangedSubview(glassStyleStackView) - itemCustomizationContainer.addArrangedSubview(UIView()) //Spacer container.addArrangedSubview(itemCustomizationContainer) container.addArrangedSubview(createLabelWithText("With Fixed Button")) - let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]], style: .glass) + let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]]) fixedButtonCommandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(fixedButtonCommandBar) @@ -302,7 +293,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(textFieldContainer) - let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]],style: .glass) + let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]]) accessoryCommandBar.translatesAutoresizingMaskIntoConstraints = false #if os(iOS) textField.inputAccessoryView = accessoryCommandBar @@ -479,23 +470,6 @@ class CommandBarDemoController: DemoController { animateCommandBarDelegateEvents = sender.isOn } - @objc func glassStyleValueChanged(sender: UISwitch!) { - isGlassStyle = sender.isOn - guard let commandBar = defaultCommandBar, - let stackView = commandBar.superview as? UIStackView, - let index = stackView.arrangedSubviews.firstIndex(of: commandBar) else { - return - } - let newCommandBar = CommandBar(itemGroups: createItemGroups(), - leadingItemGroups: [[newItem(for: .keyboard)]], - style: isGlassStyle ? .glass : .primary) - newCommandBar.delegate = self - newCommandBar.translatesAutoresizingMaskIntoConstraints = false - commandBar.removeFromSuperview() - stackView.insertArrangedSubview(newCommandBar, at: index) - defaultCommandBar = newCommandBar - } - @objc func refreshDefaultBarItems(sender: UIButton!) { defaultCommandBar?.itemGroups = createItemGroups() } From 7cf8013204c9c7f361e35ea42cc94cf47d894d3f Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 14:00:47 -0700 Subject: [PATCH 05/10] Made the demo controller command bars use glass as the default style --- .../FluentUI.Demo/Demos/CommandBarDemoController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift index 35da364fb4..546f80c129 100644 --- a/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift +++ b/Demos/FluentUIDemo_iOS/FluentUI.Demo/Demos/CommandBarDemoController.swift @@ -180,7 +180,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(createLabelWithText("Default")) - let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]]) + let commandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .keyboard)]], style: .glass) commandBar.delegate = self commandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(commandBar) @@ -275,7 +275,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(createLabelWithText("With Fixed Button")) - let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]]) + let fixedButtonCommandBar = CommandBar(itemGroups: createItemGroups(), leadingItemGroups: [[newItem(for: .copy)]], trailingItemGroups: [[newItem(for: .keyboard)]], style: .glass) fixedButtonCommandBar.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(fixedButtonCommandBar) @@ -293,7 +293,7 @@ class CommandBarDemoController: DemoController { container.addArrangedSubview(textFieldContainer) - let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]]) + let accessoryCommandBar = CommandBar(itemGroups: createItemGroups(), trailingItemGroups: [[newItem(for: .keyboard)]], style: .glass) accessoryCommandBar.translatesAutoresizingMaskIntoConstraints = false #if os(iOS) textField.inputAccessoryView = accessoryCommandBar From 8def42efe940390d1edc72e28815d9b14f2b3d87 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 17:42:43 -0700 Subject: [PATCH 06/10] PR Updates --- .../Components/CommandBar/CommandBar.swift | 66 ++++++++++++------- .../CommandBar/CommandBarTokenSet.swift | 2 +- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index 2e32acdaf4..4e56672de1 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -168,9 +168,6 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { public override func layoutSubviews() { super.layoutSubviews() - let cornerRadius = commandBarContainerStackView.bounds.height / 2 - layer.cornerRadius = cornerRadius - commandBarContainerStackView.layer.cornerRadius = cornerRadius commandBarContainerStackView.layoutIfNeeded() updateShadow() @@ -346,8 +343,6 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { leadingCommandGroupsView.isHidden = leadingCommandGroupsView.itemGroups.isEmpty trailingCommandGroupsView.isHidden = trailingCommandGroupsView.itemGroups.isEmpty - addSubview(commandBarContainerStackView) - commandBarContainerStackView.addArrangedSubview(leadingCommandGroupsView) commandBarContainerStackView.addArrangedSubview(containerView) commandBarContainerStackView.addArrangedSubview(trailingCommandGroupsView) @@ -356,12 +351,15 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { updateViewHierarchy() updateMainCommandGroupsViewConstraints() - NSLayoutConstraint.activate([ - commandBarContainerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - commandBarContainerStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - commandBarContainerStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), - commandBarContainerStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) - ]) + if style == .primary { + addSubview(commandBarContainerStackView) + NSLayoutConstraint.activate([ + commandBarContainerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + commandBarContainerStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + commandBarContainerStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + commandBarContainerStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) + ]) + } if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { // Flip the scroll view to invert scrolling direction. Flip its content back because it's already in RTL. @@ -451,10 +449,19 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { let shadowInfo = tokenSet[.shadow].shadowInfo shadowInfo.applyShadow(to: self) case .glass: +#if !os(visionOS) + if #unavailable(iOS 26) { + layer.shadowColor = CommandBarTokenSet.glassEffectShadowColor + layer.shadowOpacity = CommandBarTokenSet.glassEffectShadowOpacity + layer.shadowOffset = CommandBarTokenSet.glassEffectShadowOffset + layer.shadowRadius = CommandBarTokenSet.glassEffectShadowRadius + } +#else layer.shadowColor = CommandBarTokenSet.glassEffectShadowColor layer.shadowOpacity = CommandBarTokenSet.glassEffectShadowOpacity layer.shadowOffset = CommandBarTokenSet.glassEffectShadowOffset layer.shadowRadius = CommandBarTokenSet.glassEffectShadowRadius +#endif } } @@ -483,29 +490,38 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { private func setupGlassBackground() { var effectView: UIVisualEffectView #if os(visionOS) - effectView = getBlurEffectView() + effectView = makeBlurEffectView() #else if #available(iOS 26, *) { - effectView = getGlassEffectView() + effectView = makeGlassEffectView() } else { - effectView = getBlurEffectView() + effectView = makeBlurEffectView() } #endif effectView.translatesAutoresizingMaskIntoConstraints = false - commandBarContainerStackView.insertSubview(effectView, at: 0) + addSubview(effectView) NSLayoutConstraint.activate([ - effectView.topAnchor.constraint(equalTo: commandBarContainerStackView.topAnchor), - effectView.leadingAnchor.constraint(equalTo: commandBarContainerStackView.leadingAnchor), - effectView.bottomAnchor.constraint(equalTo: commandBarContainerStackView.bottomAnchor), - effectView.trailingAnchor.constraint(equalTo: commandBarContainerStackView.trailingAnchor) + effectView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + effectView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + effectView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + effectView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) ]) - commandBarContainerStackView.backgroundColor = .clear + + let contentView = effectView.contentView + contentView.addSubview(commandBarContainerStackView) + NSLayoutConstraint.activate([ + commandBarContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + commandBarContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + commandBarContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + commandBarContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + ]) + glassEffectView = effectView } #if !os(visionOS) @available(iOS 26, *) - private func getGlassEffectView() -> UIVisualEffectView { + private func makeGlassEffectView() -> UIVisualEffectView { let effectView = UIVisualEffectView() let glassEffect = UIGlassEffect(style: .regular) glassEffect.tintColor = tokenSet[.backgroundColor].uiColor @@ -514,7 +530,7 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } #endif // !os(visionOS) - private func getBlurEffectView() -> UIVisualEffectView { + private func makeBlurEffectView() -> UIVisualEffectView { let effectView = UIVisualEffectView() effectView.effect = UIBlurEffect(style: .systemMaterial) effectView.layer.masksToBounds = true @@ -522,9 +538,11 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } private func updateCornerRadius() { - if style == .glass, let effectView = glassEffectView { - let cornerRadius = commandBarContainerStackView.bounds.height / 2 + let cornerRadius = commandBarContainerStackView.bounds.height / 2 + layer.cornerRadius = cornerRadius + commandBarContainerStackView.layer.cornerRadius = cornerRadius + if style == .glass, let effectView = glassEffectView { if #available(iOS 26, visionOS 26, *) { effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) } else { diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift index f4ec2d81b2..61deddc208 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBarTokenSet.swift @@ -132,7 +132,7 @@ public class CommandBarTokenSet: ControlTokenSet { extension CommandBarTokenSet { static let glassEffectShadowColor: CGColor = UIColor.black.cgColor static let glassEffectShadowOpacity: Float = 0.25 - static let glassEffectShadowOffset: CGSize = CGSize(width: 0, height: -2) + static let glassEffectShadowOffset: CGSize = CGSize(width: 0, height: 2) static let glassEffectShadowRadius: CGFloat = 8 } From 90885f4bef21e588ce6a8520a4b13f12e873c1e8 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 20 Apr 2026 18:01:12 -0700 Subject: [PATCH 07/10] Simplify if let --- .../Components/CommandBar/CommandBar.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index 4e56672de1..b00b65824c 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -472,10 +472,10 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { case .glass: backgroundColor = .clear #if !os(visionOS) - if #available(iOS 26, *), let effectView = glassEffectView { + if #available(iOS 26, *), let glassEffectView { let glassEffect = UIGlassEffect(style: .regular) glassEffect.tintColor = tokenSet[.backgroundColor].uiColor - effectView.effect = glassEffect + glassEffectView.effect = glassEffect } #endif } @@ -542,11 +542,11 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { layer.cornerRadius = cornerRadius commandBarContainerStackView.layer.cornerRadius = cornerRadius - if style == .glass, let effectView = glassEffectView { + if style == .glass, let glassEffectView { if #available(iOS 26, visionOS 26, *) { - effectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) + glassEffectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius)) } else { - effectView.layer.cornerRadius = cornerRadius + glassEffectView.layer.cornerRadius = cornerRadius } } } From ef6d932c06541296e1fc50165f3f8707ad990498 Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 21 Apr 2026 10:27:50 -0700 Subject: [PATCH 08/10] PR Updates --- .../Components/CommandBar/CommandBar.swift | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index b00b65824c..610299d283 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -488,16 +488,7 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } private func setupGlassBackground() { - var effectView: UIVisualEffectView -#if os(visionOS) - effectView = makeBlurEffectView() -#else - if #available(iOS 26, *) { - effectView = makeGlassEffectView() - } else { - effectView = makeBlurEffectView() - } -#endif + let effectView = makeEffectView() effectView.translatesAutoresizingMaskIntoConstraints = false addSubview(effectView) NSLayoutConstraint.activate([ @@ -519,21 +510,20 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { glassEffectView = effectView } -#if !os(visionOS) - @available(iOS 26, *) - private func makeGlassEffectView() -> UIVisualEffectView { - let effectView = UIVisualEffectView() - let glassEffect = UIGlassEffect(style: .regular) - glassEffect.tintColor = tokenSet[.backgroundColor].uiColor - effectView.effect = glassEffect - return effectView - } -#endif // !os(visionOS) - - private func makeBlurEffectView() -> UIVisualEffectView { + private func makeEffectView() -> UIVisualEffectView { let effectView = UIVisualEffectView() effectView.effect = UIBlurEffect(style: .systemMaterial) effectView.layer.masksToBounds = true + +#if !os(visionOS) + if #available(iOS 26, *) { + let glassEffect = UIGlassEffect(style: .regular) + glassEffect.tintColor = tokenSet[.backgroundColor].uiColor + effectView.effect = glassEffect + effectView.layer.masksToBounds = false + } +#endif + return effectView } From eac99a4c21654d4790ef4ab732ec73382d4e6b1d Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 21 Apr 2026 10:38:08 -0700 Subject: [PATCH 09/10] Removed effect view creation method --- .../Components/CommandBar/CommandBar.swift | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index 610299d283..a1a14ea56f 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -488,7 +488,17 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { } private func setupGlassBackground() { - let effectView = makeEffectView() + let effectView = UIVisualEffectView() + effectView.effect = UIBlurEffect(style: .systemMaterial) + effectView.layer.masksToBounds = true +#if !os(visionOS) + if #available(iOS 26, *) { + let glassEffect = UIGlassEffect(style: .regular) + glassEffect.tintColor = tokenSet[.backgroundColor].uiColor + effectView.effect = glassEffect + effectView.layer.masksToBounds = false + } +#endif effectView.translatesAutoresizingMaskIntoConstraints = false addSubview(effectView) NSLayoutConstraint.activate([ @@ -510,23 +520,6 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { glassEffectView = effectView } - private func makeEffectView() -> UIVisualEffectView { - let effectView = UIVisualEffectView() - effectView.effect = UIBlurEffect(style: .systemMaterial) - effectView.layer.masksToBounds = true - -#if !os(visionOS) - if #available(iOS 26, *) { - let glassEffect = UIGlassEffect(style: .regular) - glassEffect.tintColor = tokenSet[.backgroundColor].uiColor - effectView.effect = glassEffect - effectView.layer.masksToBounds = false - } -#endif - - return effectView - } - private func updateCornerRadius() { let cornerRadius = commandBarContainerStackView.bounds.height / 2 layer.cornerRadius = cornerRadius From f2be5cef5fe3c64ada4a5258c32d9b4e729b56b2 Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 21 Apr 2026 13:14:21 -0700 Subject: [PATCH 10/10] PR Updates --- .../Components/CommandBar/CommandBar.swift | 76 ++++++++----------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift index a1a14ea56f..3c2e488057 100644 --- a/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift +++ b/Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift @@ -115,11 +115,6 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { super.init(frame: .zero) configureHierarchy() - - if style == .glass { - setupGlassBackground() - } - updateBackgroundColor() updateShadow() @@ -351,15 +346,41 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { updateViewHierarchy() updateMainCommandGroupsViewConstraints() - if style == .primary { - addSubview(commandBarContainerStackView) + let rootView: UIView + switch style { + case .primary: + rootView = commandBarContainerStackView + case .glass: + let effectView = UIVisualEffectView() + effectView.effect = UIBlurEffect(style: .systemMaterial) + effectView.layer.masksToBounds = true +#if !os(visionOS) + if #available(iOS 26, *) { + let glassEffect = UIGlassEffect(style: .regular) + glassEffect.tintColor = tokenSet[.backgroundColor].uiColor + effectView.effect = glassEffect + effectView.layer.masksToBounds = false + } +#endif + effectView.translatesAutoresizingMaskIntoConstraints = false + let contentView = effectView.contentView + contentView.addSubview(commandBarContainerStackView) NSLayoutConstraint.activate([ - commandBarContainerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - commandBarContainerStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - commandBarContainerStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), - commandBarContainerStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) + commandBarContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + commandBarContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + commandBarContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + commandBarContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) + glassEffectView = effectView + rootView = effectView } + addSubview(rootView) + NSLayoutConstraint.activate([ + rootView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + rootView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + rootView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + rootView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) + ]) if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { // Flip the scroll view to invert scrolling direction. Flip its content back because it's already in RTL. @@ -487,39 +508,6 @@ public class CommandBar: UIView, Shadowable, TokenizedControl { trailingCommandGroupsView.updateButtonsShown() } - private func setupGlassBackground() { - let effectView = UIVisualEffectView() - effectView.effect = UIBlurEffect(style: .systemMaterial) - effectView.layer.masksToBounds = true -#if !os(visionOS) - if #available(iOS 26, *) { - let glassEffect = UIGlassEffect(style: .regular) - glassEffect.tintColor = tokenSet[.backgroundColor].uiColor - effectView.effect = glassEffect - effectView.layer.masksToBounds = false - } -#endif - effectView.translatesAutoresizingMaskIntoConstraints = false - addSubview(effectView) - NSLayoutConstraint.activate([ - effectView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - effectView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - effectView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), - effectView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor) - ]) - - let contentView = effectView.contentView - contentView.addSubview(commandBarContainerStackView) - NSLayoutConstraint.activate([ - commandBarContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), - commandBarContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - commandBarContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - commandBarContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) - ]) - - glassEffectView = effectView - } - private func updateCornerRadius() { let cornerRadius = commandBarContainerStackView.bounds.height / 2 layer.cornerRadius = cornerRadius