Conditional Property in Swiftui

Conditionally use view in SwiftUI

The simplest way to avoid using an extra container like HStack is to annotate your body property as @ViewBuilder, like this:

@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}

Conditionally updating a second property during animation in SwiftUI

I simplified it with a trimmed circle, leading to the same result.

If change in value from + to - or vice versa, I wait for the first animation to finish before starting the second.

struct ContentView: View {

@State var gaugeValue: Double = 0.8 // now in values -1 (-45°) to +1 (+45°)

var body: some View {
VStack {
let fraction: CGFloat = abs(gaugeValue) * 0.25
let rotation = gaugeValue < 0 ? (gaugeValue * 90) - 90 : -90
Circle()
.trim(from: 0, to: fraction )
.rotation(Angle(degrees: rotation), anchor: .center)

.stroke(.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)


// Tap gesture simulates changing data
Button {
let newGaugeValue = CGFloat.random(in: -1 ... 1)

if newGaugeValue * gaugeValue < 0 { // if change of +/-
withAnimation(Animation.easeOut(duration: 1.0)) {
gaugeValue = 0
}
// delay for reaching 0 point
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} else { // no change of +/-
withAnimation(Animation.easeIn(duration: 1.0)) {
gaugeValue = newGaugeValue
}
}
} label: {
Text("New Data")
}

Text("\(gaugeValue)")
}
}
}

SwiftUI - conditional onChange

I've figured it out. I needed two variables, one per TextField: isFocused1, isFocused2. Each of them changes to true with onEditingChanged. And each onChange has if condition that checks if isFocused for this TextField is true.

Now onChange is triggered only if each TextField is being edited. I have added changing background colors to visualize focus changes.

Working code:

import SwiftUI

struct ContentView: View {
@State private var string1: String = ""
@State private var int1: Int = 0

@State private var string2: String = ""
@State private var int2: Int = 0

let multiplier: Int = 2

@State private var isFocused1 = false
@State private var isFocused2 = false

var body: some View {
VStack {
HStack {
TextField("0", text: $string1, onEditingChanged: { (changed) in
isFocused1 = changed
})
.keyboardType(.decimalPad)
.onChange(of: string1, perform: { value in
if isFocused1 {
int1 = Int(string1) ?? 0
int2 = int1 * multiplier
string2 = "\(int2)"
print("int1: \(int1)")
}
})
.background(isFocused1 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)

HStack {
Spacer()
Text("x2")
}

HStack {
TextField("0", text: $string2, onEditingChanged: { (changed) in
isFocused2 = changed
})
.keyboardType(.decimalPad)
.onChange(of: string2, perform: { value in
if isFocused2 {
int2 = Int(string2) ?? 0
int1 = int2 / multiplier
string1 = ("\(int1)")
print("int2: \(int2)")
}
})
.background(isFocused2 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
}
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

After feedback from @JoakimDanielson I've made a version with enum instead of 2 separate variables:

import SwiftUI

struct ContentView: View {

enum Focus {
case input1
case input2
}

@State private var isFocused: Focus?

@State private var string1: String = ""
@State private var int1: Int = 0

@State private var string2: String = ""
@State private var int2: Int = 0

let multiplier: Int = 2

var body: some View {
VStack {
HStack {
TextField("0", text: $string1, onEditingChanged: { (changed) in
if changed {
isFocused = Focus.input1
}
})
.keyboardType(.decimalPad)
.onChange(of: string1, perform: { value in
if isFocused == .input1 {
int1 = Int(string1) ?? 0
int2 = int1 * multiplier
string2 = "\(int2)"
print("int1: \(int1)")
}
})
.background(isFocused == .input1 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)

HStack {
Spacer()
Text("x2")
}

HStack {
TextField("0", text: $string2, onEditingChanged: { (changed) in
if changed {
isFocused = Focus.input2
}
})
.keyboardType(.decimalPad)
.onChange(of: string2, perform: { value in
if isFocused == .input2 {
int2 = Int(string2) ?? 0
int1 = int2 / multiplier
string1 = ("\(int1)")
print("int2: \(int2)")
}
})
.background(isFocused == .input2 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
}
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How do I conditionally format SwiftUI code

You could use a computed property that returns a Color based on the user's reserved property:

struct User {
var reserved : String
var name : String
}

struct ContentView: View {
@State private var user = User(reserved: "waiting", name: "Bob")

var colorForReserved : Color {
switch user.reserved {
case "yes":
return .green
case "no":
return .red
case "waiting":
return .orange
default:
return .black
}
}

var body: some View {
HStack {
Image(systemName: "person.crop.circle.badge.checkmark")
Text(user.name)
}
.foregroundColor(colorForReserved)
}
}

Note that you can avoid the default if you changed reserved to an enum rather than a String

How do you make a Button conditionally hidden or disabled?

For me it worked perfectly to set the frame's height to zero when you do not want to see it. When you want to have the calculated size, just set it to nil:

SomeView
.frame(height: isVisible ? nil : 0)

If you want to disable it in addition to hiding it, you could set .disabled with the toggled boolean.

SomeView
.frame(height: isVisible ? nil : 0)
.disabled(!isVisible)

How do I conditionally modify a button's (or other UIView) property?

User the ternary operator:

struct ContentView: View {
@State private var flag = false

var body: some View {
Text("Hello")
.background(flag ? Color.orange : Color.blue)
.onTapGesture {
self.flag.toggle()
}
}
}

Implementing a custom ViewModifier where output is conditional on concrete View type (SwiftUI)

You're right, there are some code smells in that implementation, starting with the fact that you need to write type checks to accomplish the goal. Whenever you start writing is or as? along with concrete types, you should think about abstracting to a protocol.

In your case, you need an abstraction to give you the background color, so a simple protocol like:

protocol CustomModifiable: View {
var customProp: Color { get }
}

extension Text: CustomModifiable {
var customProp: Color { .red }
}

extension TextField: CustomModifiable {
var customProp: Color { .blue }
}

, should be the way to go, and the modifier should be simplifiable along the lines of:

struct CustomModifier: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
if let customModifiable = content as? CustomModifiable {
content.background(customModifiable.customProp)
} else {
content
}
}
}

The problem is that this idiomatic approach doesn't work with SwiftUI modifiers, as the content received as an argument to the body() function is some internal type of SwiftUI that wraps the original view. This means that you can't (easily) access the actual view the modifier is applied to.

And this is why the is checks always failed, as the compiler correctly said.

Not all is lost, however, as we can work around this limitation via static properties and generics.

protocol CustomModifiable: View {
static var customProp: Color { get }
}

extension Text: CustomModifiable {
static var customProp: Color { .red }
}

extension TextField: CustomModifiable {
static var customProp: Color { .blue }
}

struct CustomModifier<T: CustomModifiable>: ViewModifier {
@ViewBuilder func body(content: Content) -> some View {
content.background(T.customProp)
}
}

extension View {
func customModifier() -> some View where Self: CustomModifiable {
modifier(CustomModifier<Self>())
}
}

The above implementation comes with a compile time benefit, as only Text and TextField are allowed to be modified with the custom modifier. If the developer tries to apply the modifier on a non-accepted type of view, they-ll get a nice Instance method 'customModifier()' requires that 'MyView' conform to 'CustomModifiable', which IMO is better than deceiving about the behaviour of the modifier (i.e. does nothing of some views).

And if you need to support more views in the future, simply add extensions that conform to your protocol.



Related Topics



Leave a reply



Submit