Skip to content

Commit

Permalink
Merge branch 'main' into removeXcodeProjects
Browse files Browse the repository at this point in the history
  • Loading branch information
mischreiber authored Aug 12, 2024
2 parents f40bf20 + d22f6c2 commit b3a46d1
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class NavigationControllerDemoController: DemoController {
addTitle(text: "Large Title with Primary style")
container.addArrangedSubview(createButton(title: "Show without accessory", action: #selector(showLargeTitle)))
container.addArrangedSubview(createButton(title: "Show with collapsible search bar", action: #selector(showLargeTitleWithShyAccessory)))
container.addArrangedSubview(createButton(title: "Show with collapsible search bar and pill segmented control", action: #selector(showLargeTitleWithShyAccessoryAndSecondaryAccessory)))
container.addArrangedSubview(createButton(title: "Show with fixed search bar", action: #selector(showLargeTitleWithFixedAccessory)))
container.addArrangedSubview(createButton(title: "Show without an avatar", action: #selector(showLargeTitleWithoutAvatar)))
container.addArrangedSubview(createButton(title: "Show with a custom leading button", action: #selector(showLargeTitleWithCustomLeadingButton)))
Expand Down Expand Up @@ -56,6 +57,9 @@ class NavigationControllerDemoController: DemoController {
addTitle(text: "Top Accessory View")
container.addArrangedSubview(createButton(title: "Show with top search bar for large screen width", action: #selector(showWithTopSearchBar)))

addTitle(text: "Top Accessory View with shy wide accessory view")
container.addArrangedSubview(createButton(title: "Show with top search bar for large screen width with a shy pill segment control", action: #selector(showWithTopSearchBarWithShySecondaryAccessoryView)))

addTitle(text: "Change Style Periodically")
container.addArrangedSubview(createButton(title: "Change the style every second", action: #selector(showSearchChangingStyleEverySecond)))
}
Expand Down Expand Up @@ -87,6 +91,10 @@ class NavigationControllerDemoController: DemoController {
presentController(withTitleStyle: .largeLeading, accessoryView: createAccessoryView(), contractNavigationBarOnScroll: true)
}

@objc func showLargeTitleWithShyAccessoryAndSecondaryAccessory() {
presentController(withTitleStyle: .largeLeading, accessoryView: createAccessoryView(), secondaryAccessoryView: createSecondaryAccessoryView(), contractNavigationBarOnScroll: true)
}

@objc func showLargeTitleWithFixedAccessory() {
presentController(withTitleStyle: .largeLeading, accessoryView: createAccessoryView(), contractNavigationBarOnScroll: false)
}
Expand Down Expand Up @@ -189,6 +197,10 @@ class NavigationControllerDemoController: DemoController {
presentController(withTitleStyle: .largeLeading, style: .system, accessoryView: createAccessoryView(with: .onSystemNavigationBar), showsTopAccessory: true, contractNavigationBarOnScroll: false)
}

@objc func showWithTopSearchBarWithShySecondaryAccessoryView() {
presentController(withTitleStyle: .largeLeading, style: .system, accessoryView: createAccessoryView(with: .onSystemNavigationBar), secondaryAccessoryView: createSecondaryAccessoryView(), showsTopAccessory: true, contractNavigationBarOnScroll: true)
}

@objc func showSearchChangingStyleEverySecond() {
presentController(withTitleStyle: .largeLeading, style: .system, accessoryView: createAccessoryView(with: .onSystemNavigationBar), showsTopAccessory: true, contractNavigationBarOnScroll: false, updateStylePeriodically: true)
}
Expand All @@ -207,6 +219,7 @@ class NavigationControllerDemoController: DemoController {
subtitle: String? = nil,
style: NavigationBar.Style = .primary,
accessoryView: UIView? = nil,
secondaryAccessoryView: UIView? = nil,
showsTopAccessory: Bool = false,
contractNavigationBarOnScroll: Bool = true,
showShadow: Bool = true,
Expand All @@ -219,6 +232,7 @@ class NavigationControllerDemoController: DemoController {
content.navigationItem.navigationBarStyle = style
content.navigationItem.navigationBarShadow = showShadow ? .automatic : .alwaysHidden
content.navigationItem.accessoryView = accessoryView
content.navigationItem.secondaryAccessoryView = secondaryAccessoryView
content.navigationItem.topAccessoryViewAttributes = NavigationBarTopSearchBarAttributes()
content.navigationItem.contentScrollView = contractNavigationBarOnScroll ? content.tableView : nil
content.showsTopAccessoryView = showsTopAccessory
Expand Down Expand Up @@ -295,6 +309,16 @@ class NavigationControllerDemoController: DemoController {
return searchBar
}

private func createSecondaryAccessoryView() -> UIView {
let segmentControl = createSegmentedControl(compatibleWith: .system)
let stackView = UIStackView()
stackView.addArrangedSubview(segmentControl)
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.backgroundColor = view.fluentTheme.color(.background1)
return stackView
}

private func createSegmentedControl(compatibleWith style: NavigationBar.Style) -> UIView {
let segmentItems: [SegmentItem] = [
SegmentItem(title: "First"),
Expand Down Expand Up @@ -481,6 +505,7 @@ class RootViewController: UIViewController, UITableViewDataSource, UITableViewDe
}

var showsTopAccessoryView: Bool = false
var secondaryAccessoryView: UIView?

var personaData: PersonaData = {
let personaData = PersonaData(name: "Kat Larsson", image: UIImage(named: "avatar_kat_larsson"))
Expand Down Expand Up @@ -835,13 +860,20 @@ class RootViewController: UIViewController, UITableViewDataSource, UITableViewDe
extension RootViewController: SearchBarDelegate {
func searchBarDidBeginEditing(_ searchBar: SearchBar) {
searchBar.progressSpinner.state.isAnimating = false
if navigationItem.secondaryAccessoryView != nil && !showsTopAccessoryView {
secondaryAccessoryView = navigationItem.secondaryAccessoryView
navigationItem.secondaryAccessoryView = nil
}
}

func searchBar(_ searchBar: SearchBar, didUpdateSearchText newSearchText: String?) {
}

func searchBarDidCancel(_ searchBar: SearchBar) {
searchBar.progressSpinner.state.isAnimating = false
if secondaryAccessoryView != nil && !showsTopAccessoryView {
navigationItem.secondaryAccessoryView = secondaryAccessoryView
}
}

func searchBarDidRequestSearch(_ searchBar: SearchBar) {
Expand Down
11 changes: 9 additions & 2 deletions ios/FluentUI/Navigation/Shy Header/ShyHeaderController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class ShyHeaderController: UIViewController {
}

private var accessoryViewObservation: NSKeyValueObservation?
private var secondaryAccessoryViewObservation: NSKeyValueObservation?

private var navigationBarCenterObservation: NSKeyValueObservation?
private var navigationBarStyleObservation: NSKeyValueObservation?
Expand Down Expand Up @@ -101,6 +102,10 @@ class ShyHeaderController: UIViewController {
accessoryViewObservation = contentViewController.navigationItem.observe(\UINavigationItem.accessoryView) { [weak self] item, _ in
self?.shyHeaderView.accessoryView = item.accessoryView
}

secondaryAccessoryViewObservation = contentViewController.navigationItem.observe(\UINavigationItem.secondaryAccessoryView) { [weak self] item, _ in
self?.shyHeaderView.secondaryAccessoryView = item.secondaryAccessoryView
}
}

required init?(coder aDecoder: NSCoder) {
Expand Down Expand Up @@ -211,8 +216,10 @@ class ShyHeaderController: UIViewController {
}

private func setupShyHeaderView() {
shyHeaderView.accessoryView = contentViewController.navigationItem.accessoryView
shyHeaderView.navigationBarShadow = contentViewController.navigationItem.navigationBarShadow
let navigationItem = contentViewController.navigationItem
shyHeaderView.accessoryView = navigationItem.accessoryView
shyHeaderView.secondaryAccessoryView = navigationItem.secondaryAccessoryView
shyHeaderView.navigationBarShadow = navigationItem.navigationBarShadow
shyHeaderView.paddingView = paddingView
shyHeaderView.parentController = self
shyHeaderView.maxHeightChanged = { [weak self] in
Expand Down
74 changes: 72 additions & 2 deletions ios/FluentUI/Navigation/Shy Header/ShyHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ShyHeaderView: UIView, TokenizedControlInternal {
tokenSet.registerOnUpdate(for: self) { [weak self] in
self?.updateColors()
}
self.initSecondaryContentStackView()
}

override func willMove(toWindow newWindow: UIWindow?) {
Expand Down Expand Up @@ -125,6 +126,13 @@ class ShyHeaderView: UIView, TokenizedControlInternal {
willSet {
accessoryView?.removeFromSuperview()
contentStackView.removeFromSuperview()
// When there is no accessoryView, the top anchor of the secondaryContentStackView should be equal to
// the top anchor of the parent view.
if let secondaryContentStackViewTopAnchorConstraint {
NSLayoutConstraint.activate([
secondaryContentStackViewTopAnchorConstraint
])
}
}
didSet {
if let newContentView = accessoryView {
Expand All @@ -135,13 +143,39 @@ class ShyHeaderView: UIView, TokenizedControlInternal {
}
}

var maxHeight: CGFloat {
var secondaryAccessoryView: UIView? {
willSet {
secondaryAccessoryView?.removeFromSuperview()
}
didSet {
if let newContentView = secondaryAccessoryView {
secondaryContentStackView.addArrangedSubview(newContentView)
}
maxHeightChanged?()
}
}

var accessoryViewHeight: CGFloat {
if accessoryView == nil {
return maxHeightNoAccessory
} else {
return contentTopInset + Constants.accessoryHeight + contentBottomInset
}
}

var secondaryAccessoryViewHeight: CGFloat {
guard let secondaryAccessoryView else {
return 0.0
}

let secondaryAccessoryViewSize = secondaryAccessoryView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return secondaryAccessoryViewSize.height
}

var maxHeight: CGFloat {
return accessoryViewHeight + secondaryAccessoryViewHeight
}

private var maxHeightNoAccessory: CGFloat {
if traitCollection.verticalSizeClass == .compact {
return traitCollection.horizontalSizeClass == .compact ? Constants.maxHeightNoAccessoryCompact : Constants.maxHeightNoAccessoryCompactForLargePhone
Expand Down Expand Up @@ -186,6 +220,9 @@ class ShyHeaderView: UIView, TokenizedControlInternal {
}

private let contentStackView = UIStackView()
private var contentStackViewHeightConstraint: NSLayoutConstraint?
private let secondaryContentStackView = UIStackView()
private var secondaryContentStackViewTopAnchorConstraint: NSLayoutConstraint?
private let shadow = Separator()

private var needsShadow: Bool {
Expand Down Expand Up @@ -222,12 +259,45 @@ class ShyHeaderView: UIView, TokenizedControlInternal {

private func initContentStackView() {
contentStackView.isLayoutMarginsRelativeArrangement = true
contentStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentStackView)
contentStackView.fitIntoSuperview(usingConstraints: true)

// When there is a accessoryView, the top anchor of the secondaryContentStackView should be equal to
// the bottom anchor of contentStackView.
if let secondaryContentStackViewTopAnchorConstraint {
NSLayoutConstraint.deactivate([
secondaryContentStackViewTopAnchorConstraint
])
}

let heightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: accessoryViewHeight)
contentStackViewHeightConstraint = heightConstraint
NSLayoutConstraint.activate([
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentStackView.topAnchor.constraint(equalTo: topAnchor),
contentStackView.bottomAnchor.constraint(equalTo: secondaryContentStackView.topAnchor),
heightConstraint
])
updateContentInsets()
contentStackView.addInteraction(UILargeContentViewerInteraction())
}

private func initSecondaryContentStackView() {
secondaryContentStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(secondaryContentStackView)
let topAnchorConstraint = secondaryContentStackView.topAnchor.constraint(equalTo: topAnchor)
secondaryContentStackViewTopAnchorConstraint = topAnchorConstraint
NSLayoutConstraint.activate([
secondaryContentStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
secondaryContentStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
topAnchorConstraint,
secondaryContentStackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])

secondaryContentStackView.addInteraction(UILargeContentViewerInteraction())
}

private func initShadow() {
let shadowView = shadow
shadowView.translatesAutoresizingMaskIntoConstraints = false
Expand Down
13 changes: 13 additions & 0 deletions ios/FluentUI/Navigation/UINavigationItem+Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import UIKit
@objc public extension UINavigationItem {
private struct AssociatedKeys {
static var accessoryView: UInt8 = 0
static var secondaryAccessoryView: UInt8 = 0
static var titleAccessory: UInt8 = 0
static var titleImage: UInt8 = 0
static var topAccessoryView: UInt8 = 0
Expand All @@ -31,6 +32,18 @@ import UIKit
}
}

/// An wide accessory view that can be shown as a subview of ShyHeaderView but doesn't have leading, trailing
/// and bottom insets. So it can appear as being part of the content view but still contract and expand as part of
/// the shy header.
var secondaryAccessoryView: UIView? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.secondaryAccessoryView) as? UIView
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.secondaryAccessoryView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

/// Defines an accessory shown after the title or subtitle in a navigation bar. When defined, this gives the indication that the title can be tapped to show additional information.
var titleAccessory: NavigationBarTitleAccessory? {
get {
Expand Down

0 comments on commit b3a46d1

Please sign in to comment.