Skip to content

Commit

Permalink
Cbowdoin/bottom sheet supports x axis constraints (microsoft#1951)
Browse files Browse the repository at this point in the history
* Add in the ability specify preferredWidth can control contraints

* Cleaned up anchor description and simplified logic

* Cleaned up whitespace and paranthesis

* Fixed bug for leading and trailing edge not support rtl languanges. Also fixed minor formatting issuees

* fixed up minor formatting issues

* Adding padding to the bottom sheet constants

* Changed anchoredEdge logic to use a switch

* Replaced magic number with global spacing token

* Avoided setting public properties internally

* Swapped sheet width logic from using a closure to use a method

* Fixed naming on anchored edge objective-c consumption and removed early return
  • Loading branch information
cbowdoin authored Feb 20, 2024
1 parent 30393f0 commit 7a1bc97
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ class BottomSheetDemoController: DemoController {
bottomSheetViewController?.preferredExpandedContentHeight = sender.isOn ? 0 : 400
}

@objc private func toggleTrailingEdge(_ sender: BooleanCell) {
bottomSheetViewController?.anchoredEdge = sender.isOn ? .trailing : .center
}

@objc private func togglePreferredWidth(_ sender: BooleanCell) {
bottomSheetViewController?.preferredWidth = sender.isOn ? 400 : 0
}

@objc private func showTransientSheet() {
let hostingVC = UIHostingController(rootView: BottomSheetDemoListContentView())

Expand Down Expand Up @@ -249,7 +257,15 @@ class BottomSheetDemoController: DemoController {
DemoItem(title: "Hide collapsed content", type: .boolean, action: #selector(toggleCollapsedContentHiding), isOn: collapsedContentHidingEnabled),
DemoItem(title: "Flexible sheet height", type: .boolean, action: #selector(toggleFlexibleSheetHeight), isOn: bottomSheetViewController?.isFlexibleHeight ?? false),
DemoItem(title: "Use custom handle accessibility label", type: .boolean, action: #selector(toggleHandleUsingCustomAccessibilityLabel), isOn: isHandleUsingCustomAccessibilityLabel),
DemoItem(title: "Full screen sheet content", type: .boolean, action: #selector(toggleFullScreenSheetContent), isOn: bottomSheetViewController?.preferredExpandedContentHeight == 0)
DemoItem(title: "Full screen sheet content", type: .boolean, action: #selector(toggleFullScreenSheetContent), isOn: bottomSheetViewController?.preferredExpandedContentHeight == 0),
DemoItem(title: "Attach to trailing edge",
type: .boolean,
action: #selector(toggleTrailingEdge),
isOn: bottomSheetViewController?.anchoredEdge == .trailing),
DemoItem(title: "Set preferred width to 400",
type: .boolean,
action: #selector(togglePreferredWidth),
isOn: bottomSheetViewController?.preferredWidth == 400)
],
[
DemoItem(title: "Show transient sheet", type: .action, action: #selector(showTransientSheet))
Expand Down
82 changes: 78 additions & 4 deletions ios/FluentUI/Bottom Sheet/BottomSheetController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public protocol BottomSheetControllerDelegate: AnyObject {
case transitioning // Sheet is between states, only used during user interaction / animation
}

/// Defines where the sheet should be postionioned relative to the screen space
@objc(MSFBottomSheetAnchorEdge) public enum BottomSheetAnchorEdge: Int {
case center // Sheet is centered on the screen
case leading // Sheet is constrained to the leading edge
case trailing // Sheet is constrained to the trailing edge
}

@objc(MSFBottomSheetController)
public class BottomSheetController: UIViewController, Shadowable, TokenizedControlInternal {

Expand Down Expand Up @@ -223,6 +230,28 @@ public class BottomSheetController: UIViewController, Shadowable, TokenizedContr
}
}

/// Setting this property will result in the sheet trying to be as close to this width as possible.
/// If the declared width is too large it will roll back to the maximum width
@objc open var preferredWidth: CGFloat = 0 {
didSet {
let shouldInvalidateLayout = (preferredWidth != oldValue) && isViewLoaded
if shouldInvalidateLayout {
view.setNeedsLayout()
}
}
}

/// Represents where the sheet should appear on the screen.
/// Defaults to being centered
@objc open var anchoredEdge: BottomSheetAnchorEdge = .center {
didSet {
guard anchoredEdge != oldValue && isViewLoaded else {
return
}
view.setNeedsLayout()
}
}

/// When enabled, users will be able to move the sheet to the hidden state by swiping down.
@objc open var allowsSwipeToHide: Bool = false

Expand Down Expand Up @@ -721,8 +750,8 @@ public class BottomSheetController: UIViewController, Shadowable, TokenizedContr
// Source of truth for the sheet frame at a given offset from the top of the root view bounds.
// The output is only meaningful once view.bounds is non-zero i.e. a layout pass has occured.
private func sheetFrame(offset: CGFloat) -> CGRect {
let availableWidth: CGFloat = view.bounds.width
let sheetWidth = shouldAlwaysFillWidth ? availableWidth : min(Constants.maxSheetWidth, availableWidth)
let sheetWidth: CGFloat = determineSheetWidth()

let sheetHeight: CGFloat

if isFlexibleHeight {
Expand All @@ -732,8 +761,50 @@ public class BottomSheetController: UIViewController, Shadowable, TokenizedContr
sheetHeight = expandedSheetHeight
}

return CGRect(origin: CGPoint(x: (view.bounds.width - sheetWidth) / 2, y: offset),
size: CGSize(width: sheetWidth, height: sheetHeight))
// Calculates the location to put the left edge of the sheet relative to the view
// For right aligned we get the width of the view offset by the sheets width and the padding
// For left aligned we only need to add in the padding
// For center aligned we need the position of the center offset by half the sheets width
let xPosition: CGFloat
let isLeftToRight: Bool = UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) == .leftToRight
switch anchoredEdge {
case .center:
xPosition = (view.bounds.width - sheetWidth) / 2
case .leading:
if isLeftToRight {
xPosition = Constants.horizontalSheetPadding
} else {
xPosition = view.bounds.width - sheetWidth - Constants.horizontalSheetPadding
}
case .trailing:
if isLeftToRight {
xPosition = view.bounds.width - sheetWidth - Constants.horizontalSheetPadding
} else {
xPosition = Constants.horizontalSheetPadding
}
}

let frame = CGRect(origin: CGPoint(x: xPosition, y: offset),
size: CGSize(width: sheetWidth, height: sheetHeight))
return frame
}

// Helper function to determine how wide the sheet should be
private func determineSheetWidth() -> CGFloat {
// Width will the fill the screen if should always fill width
// Otherwise we will try and set the size to the preferred width as long as its between the max and min width
// If its not between those we will make the maximum width size
let availableWidth: CGFloat = view.bounds.width
let maxWidth = min(Constants.maxSheetWidth, availableWidth)
let determinedWidth: CGFloat
if shouldAlwaysFillWidth {
determinedWidth = availableWidth
} else if Constants.minSheetWidth...maxWidth ~= preferredWidth {
determinedWidth = preferredWidth
} else {
determinedWidth = maxWidth
}
return determinedWidth
}

private func translationRubberBandFactor(for currentOffset: CGFloat) -> CGFloat {
Expand Down Expand Up @@ -1064,6 +1135,9 @@ public class BottomSheetController: UIViewController, Shadowable, TokenizedContr
// Minimum padding from top when the sheet is fully expanded
static let minimumTopExpandedPadding: CGFloat = 25.0

// The padding allocated to the space between the sheet and the edge when attached to the leading or trailing edge
static let horizontalSheetPadding: CGFloat = GlobalTokens.spacing(.size80)

static let expandedContentAlphaTransitionLength: CGFloat = 30

static let maxSheetWidth: CGFloat = 610
Expand Down

0 comments on commit 7a1bc97

Please sign in to comment.