Skip to content

Commit

Permalink
[SwiftUI] FluentButtonStyle (microsoft#1986)
Browse files Browse the repository at this point in the history
* WIP: FluentButtonStyle

* Fix floating buttons and add hover effect

* Update demo to use Form

* Update FluentTheme defaults

* Refactor ControlHostingView and FluentUIHostingController

* Fix ordering in project, and remove unneeded vars in ButtonTokenSet

* Fix logic for FluentTheme default

* Renamed `FluentThemedHostingController` and updated documentation

* Remove stored tokenSet property, update demo button title

* Switching to use SwiftUI.ControlSize for control sizing

* Fix outline based on `isFloatingStyle`

* New demo view that shows all buttons
  • Loading branch information
mischreiber authored Mar 29, 2024
1 parent e488bde commit 7775a9f
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 68 deletions.
4 changes: 4 additions & 0 deletions ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
8F0B81122670200300463726 /* AppCenterDistribute in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = 8F0B81112670200300463726 /* AppCenterDistribute */; };
8F0B8114267021A700463726 /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = 8F0B8113267021A700463726 /* AppCenterAnalytics */; };
8F0B8116267021A700463726 /* AppCenterCrashes in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = 8F0B8115267021A700463726 /* AppCenterCrashes */; };
92279B352B97F5DA00994D88 /* ButtonDemoController_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92279B342B97F5D900994D88 /* ButtonDemoController_SwiftUI.swift */; };
923DF2DB271158C900637646 /* libFluentUI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 923DF2DA271158C900637646 /* libFluentUI.a */; };
923DF2DF27115B4700637646 /* FluentUIResources-ios.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 923DF2DC271158CD00637646 /* FluentUIResources-ios.bundle */; };
9245E1F927BECDBB007616F3 /* GlobalColorTokensDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9245E1F827BECDBB007616F3 /* GlobalColorTokensDemoController.swift */; };
Expand Down Expand Up @@ -220,6 +221,7 @@
807E8B4428F9F8B8002B8F84 /* PillButtonDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonDemoController.swift; sourceTree = "<group>"; };
80AECC0B2630F1BB005AF2F3 /* BottomCommandingDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomCommandingDemoController.swift; sourceTree = "<group>"; };
80B1F7002628D8BB004DFEE5 /* BottomSheetDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetDemoController.swift; sourceTree = "<group>"; };
92279B342B97F5D900994D88 /* ButtonDemoController_SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonDemoController_SwiftUI.swift; sourceTree = "<group>"; };
923DF2DA271158C900637646 /* libFluentUI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFluentUI.a; sourceTree = BUILT_PRODUCTS_DIR; };
923DF2DC271158CD00637646 /* FluentUIResources-ios.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = "FluentUIResources-ios.bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
9245E1F827BECDBB007616F3 /* GlobalColorTokensDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalColorTokensDemoController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -520,6 +522,7 @@
80AECC0B2630F1BB005AF2F3 /* BottomCommandingDemoController.swift */,
80B1F7002628D8BB004DFEE5 /* BottomSheetDemoController.swift */,
B4D852DA225C010A004B1B29 /* ButtonDemoController.swift */,
92279B342B97F5D900994D88 /* ButtonDemoController_SwiftUI.swift */,
92D5598326A1523400328FD3 /* CardNudgeDemoController.swift */,
CCC18C2E2501C75F00BE830E /* CardViewDemoController.swift */,
9245E1F827BECDBB007616F3 /* GlobalColorTokensDemoController.swift */,
Expand Down Expand Up @@ -838,6 +841,7 @@
E6842974247B672000A29C40 /* SceneDelegate.swift in Sources */,
EC24DBC628B97EB70026EF92 /* PopupMenuObjCDemoController.m in Sources */,
53097D3C27028AD000A6E4DC /* HUDDemoController.swift in Sources */,
92279B352B97F5DA00994D88 /* ButtonDemoController_SwiftUI.swift in Sources */,
3ADA2E1D29C515890020434A /* MultilineCommandBarDemoController.swift in Sources */,
92E977B826C7144F008E10A8 /* DemoControllerScrollView.swift in Sources */,
92E977B726C7144F008E10A8 /* UIResponder+Extensions.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class ButtonDemoController: DemoController {

container.alignment = .leading

addTitle(text: "SwiftUI Button Demo")
container.addArrangedSubview(createButton(title: "Show", action: #selector(showSwiftUIDemo)))

for style in ButtonStyle.allCases {
for size in ButtonSizeCategory.allCases {
if style.isFloating && size == .medium {
Expand Down Expand Up @@ -247,4 +250,9 @@ extension ButtonDemoController: DemoAppearanceDelegate {
}
]
}

@objc private func showSwiftUIDemo() {
navigationController?.pushViewController(ButtonDemoControllerSwiftUI(),
animated: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//

import FluentUI
import SwiftUI
import UIKit

class ButtonDemoControllerSwiftUI: FluentThemedHostingController {
@objc required dynamic init?(coder aDecoder: NSCoder) {
preconditionFailure("init(coder:) has not been implemented")
}

init() {
super.init(rootView: AnyView(ButtonDemoView()))
self.title = "Button (SwiftUI)"
}

@MainActor required dynamic init(rootView: AnyView) {
super.init(rootView: rootView)
}

override func viewDidLoad() {
super.viewDidLoad()
configureAppearanceAndReadmePopovers()
}

// MARK: - Demo Appearance Popover

func configureAppearanceAndReadmePopovers() {
let settingsButton = UIBarButtonItem(image: UIImage(named: "ic_fluent_settings_24_regular"),
style: .plain,
target: self,
action: #selector(showAppearancePopover(_:)))
navigationItem.rightBarButtonItems = [settingsButton]
}

@objc func showAppearancePopover(_ sender: AnyObject, presenter: UIViewController) {
if let barButtonItem = sender as? UIBarButtonItem {
appearanceController.popoverPresentationController?.barButtonItem = barButtonItem
} else if let sourceView = sender as? UIView {
appearanceController.popoverPresentationController?.sourceView = sourceView
appearanceController.popoverPresentationController?.sourceRect = sourceView.bounds
}
appearanceController.popoverPresentationController?.delegate = self
presenter.present(appearanceController, animated: true, completion: nil)
}

@objc func showAppearancePopover(_ sender: AnyObject) {
showAppearancePopover(sender, presenter: self)
}

private lazy var appearanceController: DemoAppearanceController = .init(delegate: self as? DemoAppearanceDelegate)

}

extension ButtonDemoControllerSwiftUI: UIPopoverPresentationControllerDelegate {
/// Overridden to allow for popover-style modal presentation on compact (e.g. iPhone) devices.
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}

struct ButtonDemoView: View {
public var body: some View {
VStack {
demoButton(style, size, isDisabled: isDisabled)
demoOptions
}
}

@State var isDisabled: Bool = false
@State var text: String = "Button"
@State var showImage: Bool = true
@State var showLabel: Bool = true
@State var showAlert: Bool = false
@State var size: ControlSize = .large
@State var style: FluentUI.ButtonStyle = .accent

@Environment(\.fluentTheme) var fluentTheme: FluentTheme

@ViewBuilder
private func demoButton(_ buttonStyle: FluentUI.ButtonStyle, _ controlSize: ControlSize, isDisabled: Bool) -> some View {
Button(action: {
showAlert = true
}, label: {
HStack {
if showImage {
Image("Placeholder_24")
}
if showLabel && text.count > 0 {
Text(text)
}
}
})
.buttonStyle(FluentButtonStyle(style: buttonStyle))
.controlSize(controlSize)
.disabled(isDisabled)
.fixedSize()
.padding(8.0)
.alert(isPresented: $showAlert, content: {
Alert(title: Text("Button tapped"))
})
}

@ViewBuilder
private var demoOptions: some View {
Form {
Section("Content") {
HStack(alignment: .firstTextBaseline) {
Text("Button Text")
Spacer()
TextField("Text", text: $text)
.autocapitalization(.none)
.disableAutocorrection(true)
.multilineTextAlignment(.trailing)
}
.frame(maxWidth: .infinity)

FluentUIDemoToggle(titleKey: "Show image", isOn: $showImage)
FluentUIDemoToggle(titleKey: "Show label", isOn: $showLabel)
FluentUIDemoToggle(titleKey: "Disabled", isOn: $isDisabled)
}

Section("Style and Size") {
Picker("Style", selection: $style) {
ForEach(Array(FluentUI.ButtonStyle.allCases.enumerated()), id: \.element) { _, buttonStyle in
Text("\(buttonStyle.description)").tag(buttonStyle.rawValue)
}
}

Picker("Control Size", selection: $size) {
ForEach(Array(SwiftUI.ControlSize.allCases.enumerated()), id: \.element) { _, buttonSize in
Text("\(buttonSize.description)").tag(buttonSize)
}
}
}

Section("More") {
NavigationLink("All Button Styles") {
allButtonStyles
}
}
}
}

@ViewBuilder
private var allButtonStyles: some View {
ScrollView {
ForEach(Array(FluentUI.ButtonStyle.allCases.enumerated()), id: \.element) { _, buttonStyle in
Text("\(buttonStyle.description)")
.font(Font(fluentTheme.typography(.title2)))
ForEach(Array([ControlSize.small, ControlSize.regular, ControlSize.large].enumerated()), id: \.element) { _, controlSize in
HStack {
demoButton(buttonStyle, controlSize, isDisabled: false)
demoButton(buttonStyle, controlSize, isDisabled: true)
}
.frame(maxWidth: .infinity)
}
}
}
}
}

private extension ControlSize {
var description: String {
switch self {
case .mini:
return "mini"
case .small:
return "small"
case .regular:
return "regular"
case .large:
return "large"
case .extraLarge:
return "extraLarge"
@unknown default:
return "UNKNOWN"
}
}
}
12 changes: 8 additions & 4 deletions ios/FluentUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
532FE3D426EA6D74007539C0 /* ActivityIndicatorTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532FE3CE26EA6D73007539C0 /* ActivityIndicatorTokenSet.swift */; };
532FE3D626EA6D74007539C0 /* ActivityIndicatorModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532FE3CF26EA6D73007539C0 /* ActivityIndicatorModifiers.swift */; };
532FE3D826EA6D74007539C0 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532FE3D026EA6D74007539C0 /* ActivityIndicator.swift */; };
535559E42711411E0094A871 /* FluentUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535559E22711411E0094A871 /* FluentUIHostingController.swift */; };
535559E42711411E0094A871 /* FluentThemedHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535559E22711411E0094A871 /* FluentThemedHostingController.swift */; };
5373D5652694D65C0032A3B4 /* AvatarTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5373D5602694D65C0032A3B4 /* AvatarTokenSet.swift */; };
5373D5672694D65C0032A3B4 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5373D5612694D65C0032A3B4 /* Avatar.swift */; };
5373D56B2694D65C0032A3B4 /* MSFAvatarPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5373D5632694D65C0032A3B4 /* MSFAvatarPresence.swift */; };
Expand Down Expand Up @@ -173,6 +173,7 @@
92016FF8299DF34A00660DB7 /* EmptyTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92016FF7299DF34A00660DB7 /* EmptyTokenSet.swift */; };
92088EF92666DB2C003F571A /* PersonaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92088EF72666DB2C003F571A /* PersonaButton.swift */; };
920945492703DDA000B38E1A /* CardNudgeTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920945472703DDA000B38E1A /* CardNudgeTokenSet.swift */; };
92279B332B97C7DC00994D88 /* FluentButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92279B322B97C7DC00994D88 /* FluentButtonStyle.swift */; };
922A34DF27BB87990062721F /* TokenizedControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 922A34DE27BB87990062721F /* TokenizedControlView.swift */; };
9231491128BF026A001B033E /* HeadsUpDisplayTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5336B17F27FB6D9B00B01E0D /* HeadsUpDisplayTokenSet.swift */; };
9231491228BF026A001B033E /* HUDModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5336B17027F77EB700B01E0D /* HUDModifiers.swift */; };
Expand Down Expand Up @@ -300,7 +301,7 @@
5336B17327F77EB700B01E0D /* HeadsUpDisplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadsUpDisplay.swift; sourceTree = "<group>"; };
5336B17427F77EB700B01E0D /* HUD.resources.xcfilelist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcfilelist; path = HUD.resources.xcfilelist; sourceTree = "<group>"; };
5336B17F27FB6D9B00B01E0D /* HeadsUpDisplayTokenSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadsUpDisplayTokenSet.swift; sourceTree = "<group>"; };
535559E22711411E0094A871 /* FluentUIHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FluentUIHostingController.swift; sourceTree = "<group>"; };
535559E22711411E0094A871 /* FluentThemedHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FluentThemedHostingController.swift; sourceTree = "<group>"; };
5373D5602694D65C0032A3B4 /* AvatarTokenSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarTokenSet.swift; sourceTree = "<group>"; };
5373D5612694D65C0032A3B4 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
5373D5632694D65C0032A3B4 /* MSFAvatarPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSFAvatarPresence.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -347,6 +348,7 @@
92079C8E26B66E5100D688DA /* CardNudgeModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardNudgeModifiers.swift; sourceTree = "<group>"; };
92088EF72666DB2C003F571A /* PersonaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonaButton.swift; sourceTree = "<group>"; };
920945472703DDA000B38E1A /* CardNudgeTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardNudgeTokenSet.swift; sourceTree = "<group>"; };
92279B322B97C7DC00994D88 /* FluentButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluentButtonStyle.swift; sourceTree = "<group>"; };
922A34DE27BB87990062721F /* TokenizedControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenizedControlView.swift; sourceTree = "<group>"; };
9231F10229BB99090079CD94 /* FluentTheme+Tokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FluentTheme+Tokens.swift"; sourceTree = "<group>"; };
923DB9D2274CB65700D8E58A /* TokenizedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizedControl.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -665,6 +667,7 @@
children = (
A5CEC23020E451D00016922A /* Button.swift */,
EC65F78F292EDCEF002A1A23 /* ButtonTokenSet.swift */,
92279B322B97C7DC00994D88 /* FluentButtonStyle.swift */,
);
path = Button;
sourceTree = "<group>";
Expand Down Expand Up @@ -1005,8 +1008,8 @@
D6296DAD295B7C9F002E8EB6 /* ColorProviding.swift */,
926FEEA92B45A8B4002C61D0 /* Compatibility.swift */,
923DB9D6274CB66D00D8E58A /* ControlHostingView.swift */,
535559E22711411E0094A871 /* FluentThemedHostingController.swift */,
A559BB82212B7D870055E107 /* FluentUIFramework.swift */,
535559E22711411E0094A871 /* FluentUIHostingController.swift */,
EC04E65729C27359005F8BA0 /* FocusRingView.swift */,
530D9C5227EE7B2C00BDCBBF /* SwiftUI+ViewAnimation.swift */,
5373D56F2694D66F0032A3B4 /* SwiftUI+ViewModifiers.swift */,
Expand Down Expand Up @@ -1560,6 +1563,7 @@
5314E12B25F016230099271A /* PillButtonBar.swift in Sources */,
5314E07E25F00F1A0099271A /* DateTimePickerView.swift in Sources */,
5328D97726FBA3D700F3723B /* IndeterminateProgressBar.swift in Sources */,
92279B332B97C7DC00994D88 /* FluentButtonStyle.swift in Sources */,
5314E07625F00F160099271A /* DateTimePickerController.swift in Sources */,
922A34DF27BB87990062721F /* TokenizedControlView.swift in Sources */,
92016FF8299DF34A00660DB7 /* EmptyTokenSet.swift in Sources */,
Expand Down Expand Up @@ -1608,7 +1612,7 @@
5314E30225F0260E0099271A /* AccessibilityContainerView.swift in Sources */,
8035CAD026377C17007B3FD1 /* CommandingItem.swift in Sources */,
5314E0F325F012C80099271A /* ShyHeaderController.swift in Sources */,
535559E42711411E0094A871 /* FluentUIHostingController.swift in Sources */,
535559E42711411E0094A871 /* FluentThemedHostingController.swift in Sources */,
3A9FC0F72A705C090060A6BE /* PeoplePickerTokenSet.swift in Sources */,
9231491628BF02B9001B033E /* Button.swift in Sources */,
5314E1B125F01A980099271A /* TooltipView.swift in Sources */,
Expand Down
5 changes: 0 additions & 5 deletions ios/FluentUI/Button/ButtonTokenSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ public enum ButtonToken: Int, TokenSetKey {
public class ButtonTokenSet: ControlTokenSet<ButtonToken> {
init(style: @escaping () -> ButtonStyle,
size: @escaping () -> ButtonSizeCategory) {
self.style = style
self.size = size
super.init { [style, size] token, theme in
switch token {
case .backgroundColor:
Expand Down Expand Up @@ -298,9 +296,6 @@ public class ButtonTokenSet: ControlTokenSet<ButtonToken> {
}
}
}

var style: () -> ButtonStyle
var size: () -> ButtonSizeCategory
}

extension ButtonTokenSet {
Expand Down
Loading

0 comments on commit 7775a9f

Please sign in to comment.