From 6b47be2a256263df1566848596a7f665ddc01c31 Mon Sep 17 00:00:00 2001 From: Nathan Ansel Date: Thu, 21 Dec 2023 14:43:53 -0600 Subject: [PATCH 1/2] BIT-249 Adds an accessible HStack --- .../Application/Views/AccessibleHStack.swift | 82 +++++++++++++++++++ .../AddEditSendItem/AddEditSendItemView.swift | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift diff --git a/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift b/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift new file mode 100644 index 0000000000..49bd10c5e8 --- /dev/null +++ b/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift @@ -0,0 +1,82 @@ +import SwiftUI + +// MARK: - AccessibleHStack + +/// An `HStack` that will automatically convert to a `VStack` when the user's dynamic type settings +/// exceed the specified value. +/// +struct AccessibleHStack: View where Content: View { + // MARK: Private Properties + + /// The content to display in this stack. + private let content: Content + + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + + /// A flag indicating if this stack should be using an `HStack` to layout its content + /// horizontally. + private var isLayoutHorizontal: Bool { + guard let minVerticalDynamicTypeSize else { return !dynamicTypeSize.isAccessibilitySize } + return dynamicTypeSize < minVerticalDynamicTypeSize + } + + // MARK: Properties + + /// The guide for aligning the subviews in this stack. This guide has the same vertical (in an + /// `HStack`) or horizontal (in a `VStack`) screen coordinate for every subview. + let alignment: Alignment + + /// The distance between adjacent subviews, or `nil` if you want the stack to choose a default + /// distance for each pair of subviews. + let spacing: CGFloat? + + /// The minimum `DynamicTypeSize` for this view to layout its content vertically. + /// + /// Once the current `DynamicTypeSize` setting matches or exceeds this value this view's + /// `content` will be rendered in a `VStack`. If `nil` is provided, + /// `DynamicTypeSize.isAccessibilitySize` will be used to swap between `HStack` and `VStack` + /// instead. + let minVerticalDynamicTypeSize: DynamicTypeSize? + + // MARK: Initialization + + /// Creates a new `AccessibleHStack`. + /// + /// - Parameters: + /// - alignment: The guide for aligning the subviews in this stack. This guide has the same + /// vertical (in an `HStack`) or horizontal (in a `VStack`) screen coordinate for every + /// subview. Defaults to `.center`, which centers content horizontally and vertically. + /// - spacing: The distance between adjacent subviews, or `nil` if you want the stack to + /// choose a default distance for each pair of subviews. Defaults to `.nil`. + /// - minVerticalDynamicTypeSize: The `DynamicTypeSize` for this view to swap on. This value + /// should be the smallest type size that should be rendered in a `VStack`. If `nil` is + /// provided, `DynamicTypeSize.isAccessibilitySize` will be used to swap between `HStack` + /// and `VStack` instead. Defaults to `nil`. + /// - content: The `Content` to display in this stack. + /// + init( + alignment: Alignment = .center, + spacing: CGFloat? = nil, + minVerticalDynamicTypeSize: DynamicTypeSize? = nil, + @ViewBuilder content: () -> Content + ) { + self.alignment = alignment + self.spacing = spacing + self.minVerticalDynamicTypeSize = minVerticalDynamicTypeSize + self.content = content() + } + + // MARK: View + + var body: some View { + if isLayoutHorizontal { + HStack(alignment: alignment.vertical, spacing: spacing) { + content + } + } else { + VStack(alignment: alignment.horizontal, spacing: spacing) { + content + } + } + } +} diff --git a/BitwardenShared/UI/Tools/Send/AddEditSendItem/AddEditSendItemView.swift b/BitwardenShared/UI/Tools/Send/AddEditSendItem/AddEditSendItemView.swift index c53bf28cd2..db881a8cb8 100644 --- a/BitwardenShared/UI/Tools/Send/AddEditSendItem/AddEditSendItemView.swift +++ b/BitwardenShared/UI/Tools/Send/AddEditSendItem/AddEditSendItemView.swift @@ -60,7 +60,7 @@ struct AddEditSendItemView: View { ) if store.state.deletionDate == .custom { - HStack(spacing: 8) { + AccessibleHStack(alignment: .leading, spacing: 8) { BitwardenDatePicker( selection: store.binding( get: \.customDeletionDate, From 93ca426ad2a5c6189c9d2a9d61a780e973f3f9d0 Mon Sep 17 00:00:00 2001 From: Nathan Ansel Date: Wed, 3 Jan 2024 14:28:11 -0600 Subject: [PATCH 2/2] BIT-249 Fixes lint warnings --- .../Application/Views/AccessibleHStack.swift | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift b/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift index 49bd10c5e8..aef94ed9d5 100644 --- a/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift +++ b/BitwardenShared/UI/Platform/Application/Views/AccessibleHStack.swift @@ -2,7 +2,7 @@ import SwiftUI // MARK: - AccessibleHStack -/// An `HStack` that will automatically convert to a `VStack` when the user's dynamic type settings +/// An `HStack` that will automatically convert to a `VStack` when the user's dynamic type settings /// exceed the specified value. /// struct AccessibleHStack: View where Content: View { @@ -11,6 +11,7 @@ struct AccessibleHStack: View where Content: View { /// The content to display in this stack. private let content: Content + /// The current Dynamic Type size. @Environment(\.dynamicTypeSize) private var dynamicTypeSize /// A flag indicating if this stack should be using an `HStack` to layout its content @@ -26,18 +27,32 @@ struct AccessibleHStack: View where Content: View { /// `HStack`) or horizontal (in a `VStack`) screen coordinate for every subview. let alignment: Alignment - /// The distance between adjacent subviews, or `nil` if you want the stack to choose a default - /// distance for each pair of subviews. - let spacing: CGFloat? - /// The minimum `DynamicTypeSize` for this view to layout its content vertically. /// /// Once the current `DynamicTypeSize` setting matches or exceeds this value this view's - /// `content` will be rendered in a `VStack`. If `nil` is provided, + /// `content` will be rendered in a `VStack`. If `nil` is provided, /// `DynamicTypeSize.isAccessibilitySize` will be used to swap between `HStack` and `VStack` /// instead. let minVerticalDynamicTypeSize: DynamicTypeSize? + /// The distance between adjacent subviews, or `nil` if you want the stack to choose a default + /// distance for each pair of subviews. + let spacing: CGFloat? + + // MARK: View + + var body: some View { + if isLayoutHorizontal { + HStack(alignment: alignment.vertical, spacing: spacing) { + content + } + } else { + VStack(alignment: alignment.horizontal, spacing: spacing) { + content + } + } + } + // MARK: Initialization /// Creates a new `AccessibleHStack`. @@ -65,18 +80,4 @@ struct AccessibleHStack: View where Content: View { self.minVerticalDynamicTypeSize = minVerticalDynamicTypeSize self.content = content() } - - // MARK: View - - var body: some View { - if isLayoutHorizontal { - HStack(alignment: alignment.vertical, spacing: spacing) { - content - } - } else { - VStack(alignment: alignment.horizontal, spacing: spacing) { - content - } - } - } }