Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down
112 changes: 98 additions & 14 deletions Sources/FluentUI_iOS/Components/CommandBar/CommandBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -152,13 +163,11 @@ public class CommandBar: UIView, Shadowable, TokenizedControl {
public override func layoutSubviews() {
super.layoutSubviews()

let cornerRadius = bounds.height / 2
layer.cornerRadius = cornerRadius
commandBarContainerStackView.layer.cornerRadius = cornerRadius
commandBarContainerStackView.layoutIfNeeded()

updateShadow()
updateScrollViewShadow()
updateCornerRadius()
Comment thread
cbowdoin marked this conversation as resolved.
}

#if DEBUG
Expand Down Expand Up @@ -191,6 +200,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 {
Expand Down Expand Up @@ -246,6 +258,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

Expand Down Expand Up @@ -324,8 +338,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)
Expand All @@ -334,11 +346,40 @@ public class CommandBar: UIView, Shadowable, TokenizedControl {
updateViewHierarchy()
updateMainCommandGroupsViewConstraints()

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: 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([
commandBarContainerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
commandBarContainerStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
commandBarContainerStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
commandBarContainerStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)
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 {
Expand Down Expand Up @@ -424,12 +465,41 @@ 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:
#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
}
}

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 glassEffectView {
let glassEffect = UIGlassEffect(style: .regular)
glassEffect.tintColor = tokenSet[.backgroundColor].uiColor
glassEffectView.effect = glassEffect
}
#endif
}
}

private func updateButtonTokens() {
Expand All @@ -438,6 +508,20 @@ public class CommandBar: UIView, Shadowable, TokenizedControl {
trailingCommandGroupsView.updateButtonsShown()
}

private func updateCornerRadius() {
let cornerRadius = commandBarContainerStackView.bounds.height / 2
layer.cornerRadius = cornerRadius
commandBarContainerStackView.layer.cornerRadius = cornerRadius

if style == .glass, let glassEffectView {
if #available(iOS 26, visionOS 26, *) {
glassEffectView.cornerConfiguration = .corners(radius: UICornerRadius.fixed(cornerRadius))
} else {
glassEffectView.layer.cornerRadius = cornerRadius
}
}
}

/// 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 ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,11 +66,19 @@ public enum CommandBarToken: Int, TokenSetKey {

/// Design token set for the `CommandBar` control.
public class CommandBarTokenSet: ControlTokenSet<CommandBarToken> {
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) }
Expand Down Expand Up @@ -107,6 +124,16 @@ public class CommandBarTokenSet: ControlTokenSet<CommandBarToken> {
}
}
}

var style: () -> CommandBarStyle
}

// MARK: Constants
extension CommandBarTokenSet {
static let glassEffectShadowColor: CGColor = UIColor.black.cgColor
Comment thread
cbowdoin marked this conversation as resolved.
static let glassEffectShadowOpacity: Float = 0.25
static let glassEffectShadowOffset: CGSize = CGSize(width: 0, height: 2)
static let glassEffectShadowRadius: CGFloat = 8
}

// MARK: - Constants
Expand Down
Loading