* tokenize badgeField

* update name

* add back observer

* address comments
This commit is contained in:
Joanna Qu 2023-07-25 14:40:38 -04:00 коммит произвёл GitHub
Родитель 429238537d
Коммит 461c0e861b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 172 добавлений и 35 удалений

Просмотреть файл

@ -22,6 +22,7 @@ struct Demos {
DemoDescriptor("ActivityIndicator", ActivityIndicatorDemoController.self),
DemoDescriptor("Avatar", AvatarDemoController.self),
DemoDescriptor("AvatarGroup", AvatarGroupDemoController.self),
DemoDescriptor("BadgeField", BadgeFieldDemoController.self),
DemoDescriptor("BadgeView", BadgeViewDemoController.self),
DemoDescriptor("BottomCommandingController", BottomCommandingDemoController.self),
DemoDescriptor("BottomSheetController", BottomSheetDemoController.self),
@ -60,7 +61,6 @@ struct Demos {
]
static let controls: [DemoDescriptor] = [
DemoDescriptor("BadgeField", BadgeFieldDemoController.self),
DemoDescriptor("Card", CardViewDemoController.self),
DemoDescriptor("DateTimePicker", DateTimePickerDemoController.self),
DemoDescriptor("PeoplePicker", PeoplePickerDemoController.self),

Просмотреть файл

@ -7,6 +7,8 @@ import FluentUI
import UIKit
class BadgeFieldDemoController: DemoController {
private var badgeFields = [BadgeField]()
override func viewDidLoad() {
super.viewDidLoad()
@ -29,7 +31,9 @@ class BadgeFieldDemoController: DemoController {
let badgeField1 = setupBadgeField(label: "Send to:", dataSources: badgeDataSources1)
badgeField1.numberOfLines = 1
badgeFields.append(badgeField1)
let badgeField2 = setupBadgeField(label: "Cc:", dataSources: badgeDataSources2)
badgeFields.append(badgeField2)
addDescription(text: "Badge field with limited number of lines")
container.addArrangedSubview(badgeField1)
@ -66,3 +70,72 @@ extension BadgeFieldDemoController: BadgeFieldDelegate {
showMessage("\(badge.dataSource?.text ?? "A selected badge") was tapped")
}
}
extension BadgeFieldDemoController: DemoAppearanceDelegate {
func themeWideOverrideDidChange(isOverrideEnabled: Bool) {
guard let fluentTheme = self.view.window?.fluentTheme else {
return
}
fluentTheme.register(tokenSetType: BadgeFieldTokenSet.self, tokenSet: isOverrideEnabled ? themeWideOverrideBadgeFieldTokens : nil)
}
func perControlOverrideDidChange(isOverrideEnabled: Bool) {
for badgeField in badgeFields {
badgeField.tokenSet.replaceAllOverrides(with: isOverrideEnabled ? perControlOverrideBadgeFieldTokens : nil)
}
}
func isThemeWideOverrideApplied() -> Bool {
return self.view.window?.fluentTheme.tokens(for: BadgeFieldTokenSet.self)?.isEmpty == false
}
// MARK: - Custom tokens
private var themeWideOverrideBadgeFieldTokens: [BadgeFieldTokenSet.Tokens: ControlTokenValue] {
return [
.backgroundColor: .uiColor {
return UIColor(light: GlobalTokens.sharedColor(.lavender, .tint40),
dark: GlobalTokens.sharedColor(.lavender, .shade30))
},
.labelFont: .uiFont {
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
},
.placeholderFont: .uiFont {
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
},
.textFieldFont: .uiFont {
return UIFont(descriptor: .init(name: "Papyrus", size: 18.0), size: 18.0)
}
]
}
private var perControlOverrideBadgeFieldTokens: [BadgeFieldTokenSet.Tokens: ControlTokenValue] {
return [
.backgroundColor: .uiColor {
return UIColor(light: GlobalTokens.sharedColor(.orange, .tint40),
dark: GlobalTokens.sharedColor(.orange, .shade30))
},
.labelColor: .uiColor {
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
dark: GlobalTokens.sharedColor(.navy, .shade30))
},
.placeholderColor: .uiColor {
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
dark: GlobalTokens.sharedColor(.navy, .shade30))
},
.textFieldColor: .uiColor {
return UIColor(light: GlobalTokens.sharedColor(.navy, .tint40),
dark: GlobalTokens.sharedColor(.navy, .shade30))
},
.labelFont: .uiFont {
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
},
.placeholderFont: .uiFont {
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
},
.textFieldFont: .uiFont {
return UIFont(descriptor: .init(name: "Times", size: 18.0), size: 18.0)
}
]
}
}

Просмотреть файл

@ -11,6 +11,7 @@
0A8E61FB291DC11F009E412D /* CommandBarTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8E61FA291DC11F009E412D /* CommandBarTokenSet.swift */; };
0AE3041D29F721B2003CDDD9 /* TableViewHeaderFooterViewTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE3041C29F721B2003CDDD9 /* TableViewHeaderFooterViewTokenSet.swift */; };
2A9745DE281733D700E1A1FD /* TableViewCellTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9745DD281733D700E1A1FD /* TableViewCellTokenSet.swift */; };
3A9FC0F52A6AFAD40060A6BE /* BadgeFieldTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */; };
3AFB0FD629C1365600FEC1A9 /* MultilineCommandBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFB0FD529C1365600FEC1A9 /* MultilineCommandBar.swift */; };
43488C46270FAD1300124C71 /* FluentNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43488C44270FAD0200124C71 /* FluentNotification.swift */; };
4B2E373D2991CB53008929B4 /* BottomSheetTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E373C2991CB53008929B4 /* BottomSheetTokenSet.swift */; };
@ -260,6 +261,7 @@
1168630322E131CF0088B302 /* TabBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
118D9847230BBA2300BC0B72 /* TabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItem.swift; sourceTree = "<group>"; };
2A9745DD281733D700E1A1FD /* TableViewCellTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellTokenSet.swift; sourceTree = "<group>"; };
3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeFieldTokenSet.swift; sourceTree = "<group>"; };
3AFB0FD529C1365600FEC1A9 /* MultilineCommandBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineCommandBar.swift; sourceTree = "<group>"; };
43488C44270FAD0200124C71 /* FluentNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluentNotification.swift; sourceTree = "<group>"; };
497DC2D724185885008D86F8 /* PillButtonBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillButtonBar.swift; sourceTree = "<group>"; };
@ -1067,6 +1069,7 @@
isa = PBXGroup;
children = (
B45EB78F219E310F008646A2 /* BadgeField.swift */,
3A9FC0F42A6AFAD40060A6BE /* BadgeFieldTokenSet.swift */,
B4A8BBCC21BF6D6900D5E3ED /* BadgeStringExtractor.swift */,
B444D6B52183A9740002B4D4 /* BadgeView.swift */,
6FC3705D29E7707F0096B239 /* BadgeViewTokenSet.swift */,
@ -1505,6 +1508,7 @@
EC5982DA27C703EE00FD048D /* ShapeCutout.swift in Sources */,
5314E03A25F00E3D0099271A /* BadgeView.swift in Sources */,
5314E1A325F01A7C0099271A /* TableViewHeaderFooterView.swift in Sources */,
3A9FC0F52A6AFAD40060A6BE /* BadgeFieldTokenSet.swift in Sources */,
5314E30325F0260E0099271A /* AccessibleViewDelegate.swift in Sources */,
6EB4B25F270ED6B30005B808 /* BadgeLabel.swift in Sources */,
EC98E2B2298D97EC00B9DF91 /* TextFieldTokenSet.swift in Sources */,

Просмотреть файл

@ -57,18 +57,13 @@ public protocol BadgeFieldDelegate: AnyObject {
* voiceover and dynamic text sizing
*/
@objc(MSFBadgeField)
open class BadgeField: UIView {
open class BadgeField: UIView, TokenizedControlInternal {
private struct Constants {
static let badgeMarginHorizontal: CGFloat = 5
static let badgeMarginVertical: CGFloat = 5
static let emptyTextFieldString: String = ""
static let dragAndDropMinimumPressDuration: TimeInterval = 0.2
static let dragAndDropScaleAnimationDuration: TimeInterval = 0.3
static let dragAndDropScaleFactor: CGFloat = 1.10
static let dragAndDropPositioningAnimationDuration: TimeInterval = 0.2
static let labelMarginRight: CGFloat = 5
static let labelColorStyle: TextColorStyle = .secondary
static let textFieldMinWidth: CGFloat = 100
}
@objc open var label: String = "" {
@ -141,6 +136,9 @@ open class BadgeField: UIView {
@objc public weak var badgeFieldDelegate: BadgeFieldDelegate?
public typealias TokenSetKeyType = BadgeFieldTokenSet.Tokens
public var tokenSet: BadgeFieldTokenSet = .init()
var deviceOrientationIsChanging: Bool {
originalDeviceOrientation != UIDevice.current.orientation
}
@ -161,17 +159,7 @@ open class BadgeField: UIView {
@objc public init() {
super.init(frame: .zero)
NotificationCenter.default.addObserver(self,
selector: #selector(themeDidChange),
name: .didChangeTheme,
object: nil)
updateBackgroundColor()
labelView.colorStyle = Constants.labelColorStyle
addSubview(labelView)
placeholderView.colorStyle = Constants.labelColorStyle
addSubview(placeholderView)
updateLabelsVisibility()
@ -200,17 +188,29 @@ open class BadgeField: UIView {
// An accessible container must set isAccessibilityElement to false
isAccessibilityElement = false
}
@objc private func themeDidChange(_ notification: Notification) {
guard let themeView = notification.object as? UIView, self.isDescendant(of: themeView) else {
return
// Update appearance whenever `tokenSet` changes.
tokenSet.registerOnUpdate(for: self) { [weak self] in
self?.updateAppearance()
}
updateBackgroundColor()
}
private func updateBackgroundColor() {
backgroundColor = fluentTheme.color(.background1)
private func updateAppearance() {
updateColors()
updateFonts()
}
private func updateColors() {
backgroundColor = tokenSet[.backgroundColor].uiColor
labelView.textColor = tokenSet[.labelColor].uiColor
placeholderView.textColor = tokenSet[.placeholderColor].uiColor
textField.textColor = tokenSet[.textFieldColor].uiColor
}
private func updateFonts() {
labelView.font = tokenSet[.labelFont].uiFont
placeholderView.font = tokenSet[.placeholderFont].uiFont
textField.font = tokenSet[.textFieldFont].uiFont
}
public required init?(coder aDecoder: NSCoder) {
@ -246,7 +246,6 @@ open class BadgeField: UIView {
}
private func setupTextField(_ textField: UITextField) {
textField.font = fluentTheme.typography(.body1)
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .emailAddress
@ -291,7 +290,7 @@ open class BadgeField: UIView {
let textFieldSize = textField.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
let textFieldHeight = ceil(textFieldSize.height)
let textFieldVerticalOffset = ceil((badgeHeight - textFieldHeight) / 2.0)
let shouldAppendToCurrentLine = left + Constants.textFieldMinWidth <= frame.width
let shouldAppendToCurrentLine = left + BadgeFieldTokenSet.textFieldMinWidth <= frame.width
if !shouldAppendToCurrentLine {
lineIndex += 1
left = 0
@ -316,6 +315,14 @@ open class BadgeField: UIView {
return CGSize(width: UIView.noIntrinsicMetric, height: contentHeight(forBoundingWidth: frame.width))
}
open override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
guard let newWindow else {
return
}
tokenSet.update(newWindow.fluentTheme)
}
private func shouldDragBadge(_ badge: BadgeView) -> Bool {
if allowsDragAndDrop {
return badgeFieldDelegate?.badgeField?(self, shouldDragBadge: badge) ?? true
@ -355,7 +362,7 @@ open class BadgeField: UIView {
if !moreBadges.isEmpty {
let moreBadgesDataSources = moreBadges.compactMap { $0.dataSource }
let moreBadge = createMoreBadge(withDataSources: moreBadgesDataSources)
moreBadgeOffset = Constants.badgeMarginHorizontal + width(forBadge: moreBadge,
moreBadgeOffset = BadgeFieldTokenSet.badgeMarginHorizontal + width(forBadge: moreBadge,
isFirstBadge: false,
isFirstBadgeOfLastDisplayedLine: false,
moreBadgeOffset: 0,
@ -388,7 +395,7 @@ open class BadgeField: UIView {
}
// Badge should be appended on current line: append and keep looping on following badges
constrainedBadges.append(badge)
return (false, currentLeft + badgeWidth + Constants.badgeMarginHorizontal, currentLineIndex)
return (false, currentLeft + badgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal, currentLineIndex)
}
private func frameForBadge(_ badgeToInsert: BadgeView, boundingWidth: CGFloat) -> CGRect {
@ -430,7 +437,7 @@ open class BadgeField: UIView {
if shouldUseConstrainedBadges, let moreBadge = moreBadge {
let moreBadgeSize = moreBadge.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
let moreBadgeWidth = ceil(moreBadgeSize.width)
return moreBadgeWidth + Constants.badgeMarginHorizontal
return moreBadgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal
} else {
return 0
}
@ -438,8 +445,8 @@ open class BadgeField: UIView {
@objc open func heightThatFits(badgeHeight: CGFloat, numberOfLines: Int) -> CGFloat {
// Vertical margin not applicable to top line
let heightForAllLines = CGFloat(numberOfLines) * (badgeHeight + Constants.badgeMarginVertical)
return heightForAllLines - Constants.badgeMarginVertical
let heightForAllLines = CGFloat(numberOfLines) * (badgeHeight + BadgeFieldTokenSet.badgeMarginVertical)
return heightForAllLines - BadgeFieldTokenSet.badgeMarginVertical
}
private func contentHeight(forBoundingWidth boundingWidth: CGFloat) -> CGFloat {
@ -454,7 +461,7 @@ open class BadgeField: UIView {
let isFirstResponderOrHasTextFieldContent = isFirstResponder || !textFieldContent.isEmpty
if isEditable && isFirstResponderOrHasTextFieldContent && left + Constants.textFieldMinWidth > boundingWidth {
if isEditable && isFirstResponderOrHasTextFieldContent && left + BadgeFieldTokenSet.textFieldMinWidth > boundingWidth {
lineIndex += 1
}
let textFieldSize = textField.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
@ -489,13 +496,13 @@ open class BadgeField: UIView {
width: badgeWidth,
height: badgeHeight)
left += badgeWidth + Constants.badgeMarginHorizontal
left += badgeWidth + BadgeFieldTokenSet.badgeMarginHorizontal
return badgeFrame
}
private func offsetForLine(badgeHeight: CGFloat, at lineIndex: Int) -> CGFloat {
return CGFloat(lineIndex) * (badgeHeight + Constants.badgeMarginVertical)
return CGFloat(lineIndex) * (badgeHeight + BadgeFieldTokenSet.badgeMarginVertical)
}
// MARK: Badges
@ -734,7 +741,7 @@ open class BadgeField: UIView {
private let placeholderView = Label()
private var labelViewRightOffset: CGFloat {
return labelView.isHidden ? 0 : labelViewWidth + Constants.labelMarginRight
return labelView.isHidden ? 0 : labelViewWidth + BadgeFieldTokenSet.labelMarginRight
}
private var labelViewWidth: CGFloat {

Просмотреть файл

@ -0,0 +1,53 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
import UIKit
/// Design token set for the `BadgeField` control.
public class BadgeFieldTokenSet: ControlTokenSet<BadgeFieldTokenSet.Tokens> {
public enum Tokens: TokenSetKey {
/// The background color of the BadgeField.
case backgroundColor
/// The color of the BadgeField's label.
case labelColor
/// The color of the BadgeField's placeholder.
case placeholderColor
/// The color of the BadgeField's text field.
case textFieldColor
/// The font of the BadgeField's label.
case labelFont
/// The font of the BadgeField's placeholder.
case placeholderFont
/// The font of the BadgeField's text field.
case textFieldFont
}
init() {
super.init { token, theme in
switch token {
case .backgroundColor:
return .uiColor { theme.color(.background1) }
case .labelColor, .placeholderColor, .textFieldColor:
return .uiColor { theme.color(.foreground2) }
case .labelFont, .placeholderFont, .textFieldFont:
return .uiFont { theme.typography(.body1) }
}
}
}
}
// MARK: Constants
extension BadgeFieldTokenSet {
static let badgeMarginHorizontal: CGFloat = 5
static let badgeMarginVertical: CGFloat = 5
static let labelMarginRight: CGFloat = 5
static let textFieldMinWidth: CGFloat = 100
}