Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/segmented picker animation #10

Merged
merged 34 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
cee9c18
WIP: Starting work on animation for segemented picker
Nov 6, 2024
89240fe
Create SegmentedBetterPickerStyle mimicing PickerStyle.segmented
Nov 8, 2024
4113431
Use IndexSet instead of [Int] for selectedIndicies
Nov 8, 2024
fa11298
Update naming for selectionIndexSet
Nov 8, 2024
f4c2634
Merge branch 'feature/swiftformat' into feature/segmented-picker-anim…
Nov 8, 2024
83b9046
Swiftformat
Nov 8, 2024
cf22821
Fix issue with dark mode
Nov 8, 2024
f32377b
Remove attempt at using colorScheme to create dark mode Picker
Nov 8, 2024
c329a46
Cleanup SegmentedBetterPickerStyle_Previews
Nov 8, 2024
440c016
swiftformat
Nov 8, 2024
9644f8c
Add @ViewBuilder when using conditional to create a View
Nov 8, 2024
f284a7c
Fix older Swift error
Nov 8, 2024
f05bd08
Fix warnings in Preview
Nov 8, 2024
970a05f
Attempt to fix Preview erors in CI
Nov 8, 2024
d38b000
Fix preview spacing
Nov 8, 2024
bb58d64
Add base for Segmented in Shared app
Nov 8, 2024
3016039
Add SegmentedUITests file
Nov 8, 2024
e4cd8a9
Add SegmentedUITests and supporting files
Nov 12, 2024
9ba6d24
Remove unused file
Nov 12, 2024
2e1fa29
swiftlint and swiftformat
Nov 12, 2024
5108b80
Remove unused scheme
Nov 12, 2024
a0adfca
Attempt to output .xcresult file as GitHub Actions artifact
Nov 12, 2024
cc74b07
Standardize github action versioning
Nov 12, 2024
6c2c58a
Revert saving xcresult as artifact and turn off -quiet in xcodebuild
Nov 12, 2024
87fcb84
MainActor isolate flaky test
Nov 12, 2024
56c217f
Merge branch 'feature/fix-test-warnings' into feature/segmented-picke…
Nov 13, 2024
bcf1958
Merge
Nov 13, 2024
614463a
Merge remote-tracking branch 'origin' into feature/segmented-picker-a…
Nov 13, 2024
74a6a86
Cleanup CI
Nov 13, 2024
4f4b2f1
Change from var to let for static properties
Nov 14, 2024
c732fc6
Add dividers between ItemCells
Nov 14, 2024
d711236
Fix frame height issue when no cell is currently selected
Nov 14, 2024
a16c5f1
swiftlint and swiftformat
Nov 14, 2024
24c6288
Cleanup preview
Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
49E598B127B230AF00C1F2CF /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598B027B230AF00C1F2CF /* ExampleApp.swift */; };
49E598B827B230AF00C1F2CF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49E598B727B230AF00C1F2CF /* Preview Assets.xcassets */; };
49E598CC27B230AF00C1F2CF /* SingleValueUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */; };
94AB9E8C2CE39FDC008967E2 /* SegmentedUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94AB9E8B2CE39FD5008967E2 /* SegmentedUITests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -60,6 +61,7 @@
49E598C727B230AF00C1F2CF /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueUITests.swift; sourceTree = "<group>"; };
49E598DB27B2312500C1F2CF /* swiftui-pick-better */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swiftui-pick-better"; path = ..; sourceTree = "<group>"; };
94AB9E8B2CE39FD5008967E2 /* SegmentedUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedUITests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -177,6 +179,7 @@
49E598CA27B230AF00C1F2CF /* ExampleUITests */ = {
isa = PBXGroup;
children = (
94AB9E8B2CE39FD5008967E2 /* SegmentedUITests.swift */,
4994CA8727EE729C00F54306 /* MutliValueUITests.swift */,
49E598CB27B230AF00C1F2CF /* SingleValueUITests.swift */,
4994CA6F27EE23C100F54306 /* SingleOptionalValueTests.swift */,
Expand Down Expand Up @@ -403,6 +406,7 @@
49A241AE2800E7BA003DD15F /* XCUIElement+Trigger.swift in Sources */,
4994CA7027EE23C100F54306 /* SingleOptionalValueTests.swift in Sources */,
4994CA8027EE2B6E00F54306 /* XCUIElementQuery+ExactlyOneMatching.swift in Sources */,
94AB9E8C2CE39FDC008967E2 /* SegmentedUITests.swift in Sources */,
4994CA8927EE72B300F54306 /* MutliValueUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
23 changes: 23 additions & 0 deletions Example/ExampleUITests/ExampleUITestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ class ExampleUITestCase: XCUITestCase {
try allElements().matching(identifier: "multiValueSectionNavItem").firstMatch
}

func segmentedNavItem() throws -> XCUIElement {
try allElements().matching(identifier: "segmentedNavItem").firstMatch
}

func singleValuePicker() throws -> XCUIElement {
try allElements().matching(identifier: "singleValuePicker").exactlyOneMatch()
}
Expand All @@ -91,6 +95,25 @@ class ExampleUITestCase: XCUITestCase {
try allElements().matching(identifier: "multiValuePicker").exactlyOneMatch()
}

func segmentedPicker() throws -> XCUIElement {
try allElements().matching(identifier: "segmentedPicker").firstMatch
}

func segmentedButtonZero() throws -> XCUIElement {
try allElements().children(matching: .button)
.element(matching: elementCompoundOrPredicate(labeled: ["Segmented 0"]))
}

func segmentedButtonOne() throws -> XCUIElement {
try allElements().children(matching: .button)
.element(matching: elementCompoundOrPredicate(labeled: ["Segmented 1"]))
}

func segmentedButtonTwo() throws -> XCUIElement {
try allElements().children(matching: .button)
.element(matching: elementCompoundOrPredicate(labeled: ["Segmented 2"]))
}

func cellZero() throws -> XCUIElement {
try allElements().children(matching: .cell)
.element(matching: elementCompoundOrPredicate(labeled: ["Cell - 0", "Cell - 0, Selected"]))
Expand Down
65 changes: 65 additions & 0 deletions Example/ExampleUITests/SegmentedUITests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SegmentedUITests.swift
// PickBetter
//
// Copyright © 2024 MFB Technologies, Inc. All rights reserved. All rights reserved.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

import XCTest
#if os(iOS)
import Example
#elseif os(macOS)
import Example_macOS
#endif

final class SegmentedUITests: ExampleUITestCase {
@MainActor
override var _sectionNavItem: (@MainActor () throws -> XCUIElement)? {
segmentedNavItem
}

@MainActor
override var _picker: (@MainActor () throws -> XCUIElement)? {
segmentedPicker
}

func testSelectSegmentedPickerItems() async throws {
_ = try sectionNavItem().waitForExistence(timeout: 1)
XCTAssert(try sectionNavItem().exists, "Navigation link/tab for 'Segmented' picker must exist")
try sectionNavItem().trigger()

_ = try picker().waitForExistence(timeout: 1)
XCTAssert(try picker().exists, "Segmented picker must exist")
_ = try segmentedButtonZero().waitForExistence(timeout: 1)
_ = try segmentedButtonOne().waitForExistence(timeout: 1)

XCTAssert(try segmentedButtonZero().exists, "Cell Zero must exist")
XCTAssert(try segmentedButtonOne().exists, "Cell One must exist")
XCTAssert(
try segmentedButtonZero().isSelected && (!segmentedButtonOne().isSelected),
"Initial state should have cell Zero selected"
)

try segmentedButtonOne().trigger()
XCTAssert(try segmentedButtonOne().isSelected, "'1' should be selected after being tapped")
XCTAssert(try !segmentedButtonZero().isSelected, "'0' should not be selected before being tapped.")

try segmentedButtonTwo().trigger()
XCTAssert(
try segmentedButtonTwo().isSelected,
"'2' should still be selected without being tapped again to deselect"
)
XCTAssert(try !segmentedButtonOne().isSelected, "'1' should be selected after being tapped.")

try segmentedButtonOne().trigger()
XCTAssert(
try segmentedButtonOne().isSelected,
"'1' should still be selected without being tapped again to deselect"
)
XCTAssert(
try !segmentedButtonTwo().isSelected,
"'2' should no longer be selected after being tapped again to deselect"
)
}
}
21 changes: 21 additions & 0 deletions Example/Shared/Sources/Shared/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public struct ContentView: View {
private var singleSectionNavItem: String { "singleValueSectionNavItem" }
private var singleOptionalSectionNavItem: String { "singleOptionalValueSectionNavItem" }
private var multiSectionNavItem: String { "multiValueSectionNavItem" }
private var segmentedNavItem: String { "segmentedNavItem" }

// MARK: Content

Expand Down Expand Up @@ -111,6 +112,22 @@ public struct ContentView: View {
#endif
}

private func segmentedPicker() -> some View {
SegmentedPickerView(items: Array(items.prefix(3)))
}

private var segmentedNavLabel: some View {
#if os(iOS)
Image(systemName: "s.circle")
#elseif os(macOS) || os(tvOS)
HStack {
Image(systemName: "n.circle")
Text("Segmented")
}
.font(.title)
#endif
}

private func navigationButton<Label>(label: Label, tag: TabOption, id: String) -> some View where Label: View {
accessibleNavigation(tag: tag, id: id) {
#if os(iOS)
Expand Down Expand Up @@ -162,6 +179,7 @@ public struct ContentView: View {
id: singleOptionalSectionNavItem
)
navigationButton(label: multiValueNavLabel, tag: .multiValue, id: multiSectionNavItem)
navigationButton(label: segmentedNavLabel, tag: .segmented, id: segmentedNavItem)
Spacer()
}
.frame(width: 75)
Expand All @@ -174,6 +192,7 @@ public struct ContentView: View {
id: singleOptionalSectionNavItem
)
navigationButton(label: multiValueNavLabel, tag: .multiValue, id: multiSectionNavItem)
navigationButton(label: segmentedNavLabel, tag: .segmented, id: segmentedNavItem)
}
#elseif os(tvOS)
TabView {
Expand Down Expand Up @@ -202,6 +221,8 @@ public struct ContentView: View {
singleOptionalValue()
case .multiValue:
multiValue()
case .segmented:
segmentedPicker()
}
Spacer()
}
Expand Down
44 changes: 44 additions & 0 deletions Example/Shared/Sources/Shared/SegmentedPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SegmentedPicker.swift
// PickBetter
//
// Copyright © 2024 MFB Technologies, Inc. All rights reserved. All rights reserved.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

import Foundation
import PickBetter
import SwiftUI

public struct SegmentedPickerView: View {
private let items: [Item]
@State private var selection: Item.ID = 0

public init(items: [Item]) {
self.items = items
}

// MARK: Accessibility Ids

private var segmentedPickerString: String { "segmentedPicker" }

public struct SegmentedItemLabel: View {
private let itemId: Int

public init(itemId: Int) {
self.itemId = itemId
}

public var body: some View {
Text("Segmented \(itemId)")
}
}

public var body: some View {
LazyView(
BetterPicker(items, selection: $selection, content: { SegmentedItemLabel(itemId: $0.id) })
.betterPickerStyle(SegmentedBetterPickerStyle(frameWidth: 500.0))
.accessibilityIdentifier(segmentedPickerString)
)
}
}
1 change: 1 addition & 0 deletions Example/Shared/Sources/Shared/TabOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public enum TabOption: Hashable, Sendable {
case singleValue
case singleOptionalValue
case multiValue
case segmented
}
1 change: 1 addition & 0 deletions Sources/PickBetter/BetterPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public struct BetterPicker<SelectionBox, ItemContent>: View where SelectionBox:
listOutput: AnyView(forEachStyledItems),
cellCount: items.count,
selectionCount: selection.count,
selectionIndexSet: IndexSet(items.enumerated().filter { selection.contains($1.0) }.map(\.offset)),
selectionLabels: items.filter { selection.contains($0.0) }.map { AnyView($0.1()) }
)
let styledBody = style.makeView(configuration)
Expand Down
4 changes: 2 additions & 2 deletions Sources/PickBetter/BetterPickerStyleCellConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import SwiftUI
/// Configuration payload passed to `BetterPickerStyle.makeListCell(_:)`
public struct BetterPickerStyleListCellConfiguration {
/// Callback for when the cell is selected
private let select: () -> Void
public let select: () -> Void

/// Callback for when the cell is deselected
private let deselect: () -> Void
public let deselect: () -> Void

/// Label for the cell
public let label: () -> AnyView
Expand Down
3 changes: 3 additions & 0 deletions Sources/PickBetter/BetterPickerStyleConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public struct BetterPickerStyleConfiguration {
/// The number of selections
public let selectionCount: Int

/// The indicies of all selected items
public let selectionIndexSet: IndexSet

/// The label views for the selected cells
public let selectionLabels: [AnyView]
}
Loading
Loading