Skip to content

Commit

Permalink
Add conditional view modifier code
Browse files Browse the repository at this point in the history
  • Loading branch information
philippzagar committed Feb 27, 2024
1 parent 1cf188c commit 5e08d98
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 0 deletions.
86 changes: 86 additions & 0 deletions Sources/SpeziViews/ViewModifier/ConditionalModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SwiftUI


extension View {
/// Applies the given transform if the given condition evaluates to `true`.
///
/// ### Usage
///
/// ```swift
/// struct ConditionalModifierTestView: View {
/// @State var condition = false
///
/// var body: some View {
/// VStack {
/// Text("Condition present")
/// .if(condition) { view in
/// view
/// .hidden()
/// }
///
/// Button("Toggle Condition") {
/// condition.toggle()
/// }
/// }
/// }
/// }
/// ```
///
/// - Parameters:
/// - condition: The condition to evaluate.
/// - transform: The transform to apply to the source `View`.
/// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
@ViewBuilder public func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}

/// Applies the given transform if the given condition closure evaluates to `true`.
///
/// ### Usage
///
/// ```swift
/// struct ConditionalModifierTestView: View {
/// @State var closureCondition = false
///
/// var body: some View {
/// VStack {
/// Text("Closure Condition present")
/// .if(condition: {
/// closureCondition
/// }, transform: { view in
/// view
/// .hidden()
/// })
///
/// Button("Toggle Closure Condition") {
/// closureCondition.toggle()
/// }
/// }
/// }
/// }
/// ```
///
/// - Parameters:
/// - condition: The condition closure to evaluate.
/// - transform: The transform to apply to the source `View`.
/// - Returns: Either the original `View` or the modified `View` if the condition closure is `true`.
@ViewBuilder public func `if`<Content: View>(condition: () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
transform(self)
} else {
self
}
}
}
12 changes: 12 additions & 0 deletions Tests/UITests/TestApp/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
},
"CanvasTest" : {

},
"Closure Condition present" : {

},
"Condition present" : {

},
"Credentials" : {

Expand Down Expand Up @@ -153,6 +159,12 @@
},
"This is a label ...\nAn other text. This is longer and we can check if the justified text works as expected. This is a very long text." : {

},
"Toggle Closure Condition" : {

},
"Toggle Condition" : {

},
"Username" : {

Expand Down
54 changes: 54 additions & 0 deletions Tests/UITests/TestApp/ViewsTests/ConditionalModifierTestView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SpeziViews
import SwiftUI


struct ConditionalModifierTestView: View {
@State var condition = false
@State var closureCondition = false


var body: some View {
VStack {
Text("Condition present")
.if(condition) { view in
view
.hidden()
}

Button("Toggle Condition") {
condition.toggle()
}
.buttonStyle(.borderedProminent)
.padding(.bottom, 20)

Divider()

Text("Closure Condition present")
.if(condition: {
closureCondition
}, transform: { view in
view
.hidden()
})
.padding(.top, 20)

Button("Toggle Closure Condition") {
condition.toggle()
}
.buttonStyle(.borderedProminent)
}
}
}


#Preview {
ConditionalModifierTestView()
}
8 changes: 8 additions & 0 deletions Tests/UITests/TestApp/ViewsTests/SpeziViewsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum SpeziViewsTests: String, TestAppTests {
case viewState = "View State"
case operationState = "Operation State"
case viewStateMapper = "View State Mapper"
case conditionalModifier = "Conditional Modifier"
case defaultErrorOnly = "Default Error Only"
case defaultErrorDescription = "Default Error Description"
case asyncButton = "Async Button"
Expand Down Expand Up @@ -98,6 +99,11 @@ enum SpeziViewsTests: String, TestAppTests {
private var viewStateMapper: some View {
ViewStateMapperTestView()
}

@ViewBuilder
private var conditionalModifier: some View {
ConditionalModifierTestView()
}

@ViewBuilder
private var defaultErrorOnly: some View {
Expand Down Expand Up @@ -146,6 +152,8 @@ enum SpeziViewsTests: String, TestAppTests {
operationState
case .viewStateMapper:
viewStateMapper
case .conditionalModifier:
conditionalModifier
case .defaultErrorOnly:
defaultErrorOnly
case .defaultErrorDescription:
Expand Down
32 changes: 32 additions & 0 deletions Tests/UITests/TestAppUITests/SpeziViews/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,38 @@ final class ModelTests: XCTestCase {
#endif
XCTAssert(content.contains("Operation State: error"))
}

func testConditionalModifier() throws {
let app = XCUIApplication()

XCTAssert(app.buttons["Conditional Modifier"].waitForExistence(timeout: 2))
app.buttons["Conditional Modifier"].tap()

XCTAssert(app.staticTexts["Condition present"].waitForExistence(timeout: 1))
XCTAssert(app.staticTexts["Closure Condition present"].waitForExistence(timeout: 1))

// Check regular condition
XCTAssert(app.buttons["Toggle Condition"].waitForExistence(timeout: 1))
app.buttons["Toggle Condition"].tap()

XCTAssert(!app.staticTexts["Condition present"].waitForExistence(timeout: 2))

XCTAssert(app.buttons["Toggle Condition"].waitForExistence(timeout: 1))
app.buttons["Toggle Condition"].tap()

XCTAssert(app.staticTexts["Condition present"].waitForExistence(timeout: 2))

// Check closure condition
XCTAssert(app.buttons["Toggle Closure Condition"].waitForExistence(timeout: 1))
app.buttons["Toggle Closure Condition"].tap()

XCTAssert(!app.staticTexts["Closure Condition present"].waitForExistence(timeout: 2))

XCTAssert(app.buttons["Toggle Closure Condition"].waitForExistence(timeout: 1))
app.buttons["Toggle Closure Condition"].tap()

XCTAssert(app.staticTexts["Closure Condition present"].waitForExistence(timeout: 2))
}

func testDefaultErrorDescription() throws {
let app = XCUIApplication()
Expand Down
4 changes: 4 additions & 0 deletions Tests/UITests/UITests.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
2FB099B82A8AD25300B20952 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2FB099B72A8AD25100B20952 /* Localizable.xcstrings */; };
9731B58F2B167053007676C0 /* ViewStateMapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9731B58E2B167053007676C0 /* ViewStateMapperView.swift */; };
977CF55C2AD2B92C006D9B54 /* XCTestApp in Frameworks */ = {isa = PBXBuildFile; productRef = 977CF55B2AD2B92C006D9B54 /* XCTestApp */; };
97A0A5102B8D7FD7006102EF /* ConditionalModifierTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A0A50F2B8D7FD7006102EF /* ConditionalModifierTestView.swift */; };
97EE16AC2B16D5AB004D25A3 /* OperationStateTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97EE16AB2B16D5AB004D25A3 /* OperationStateTestView.swift */; };
A95B6E652AF4298500919504 /* SpeziPersonalInfo in Frameworks */ = {isa = PBXBuildFile; productRef = A95B6E642AF4298500919504 /* SpeziPersonalInfo */; };
A963ACAC2AF4683A00D745F2 /* SpeziValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A963ACAB2AF4683A00D745F2 /* SpeziValidationTests.swift */; };
Expand Down Expand Up @@ -67,6 +68,7 @@
2FB0758A299DDB9000C0B37F /* TestApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestApp.xctestplan; sourceTree = "<group>"; };
2FB099B72A8AD25100B20952 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
9731B58E2B167053007676C0 /* ViewStateMapperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewStateMapperView.swift; sourceTree = "<group>"; };
97A0A50F2B8D7FD7006102EF /* ConditionalModifierTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalModifierTestView.swift; sourceTree = "<group>"; };
97EE16AB2B16D5AB004D25A3 /* OperationStateTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationStateTestView.swift; sourceTree = "<group>"; };
A963ACAB2AF4683A00D745F2 /* SpeziValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeziValidationTests.swift; sourceTree = "<group>"; };
A963ACB12AF4709400D745F2 /* XCUIApplication+Targets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+Targets.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -169,6 +171,7 @@
2FA9485E29DE90720081C086 /* ViewStateTestView.swift */,
9731B58E2B167053007676C0 /* ViewStateMapperView.swift */,
97EE16AB2B16D5AB004D25A3 /* OperationStateTestView.swift */,
97A0A50F2B8D7FD7006102EF /* ConditionalModifierTestView.swift */,
2FA9485F29DE90720081C086 /* MarkdownViewTestView.swift */,
2FA9486029DE90720081C086 /* CanvasTestView.swift */,
2FA9486329DE90720081C086 /* GeometryReaderTestView.swift */,
Expand Down Expand Up @@ -357,6 +360,7 @@
2FA9486A29DE90720081C086 /* GeometryReaderTestView.swift in Sources */,
A9F85B722B32A052005F16E6 /* NameFieldsExample.swift in Sources */,
2FA9486729DE90720081C086 /* CanvasTestView.swift in Sources */,
97A0A5102B8D7FD7006102EF /* ConditionalModifierTestView.swift in Sources */,
A963ACAC2AF4683A00D745F2 /* SpeziValidationTests.swift in Sources */,
A9FBAE982AF446F3001E4AF1 /* SpeziPersonalInfoTests.swift in Sources */,
2FA7382C290ADFAA007ACEB9 /* TestApp.swift in Sources */,
Expand Down

0 comments on commit 5e08d98

Please sign in to comment.