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
How to Convert Timeinterval into Minutes, Seconds and Milliseconds in Swift
How to Override Trait Collection for Initial Uiviewcontroller? (With Storyboard)
How to Get Directory Size with Swift on Os X
Class 'Viewcontroller' Has No Initializers in Swift
Convert Avaudiopcmbuffer to Nsdata and Back
Hide/Show Tab Bar When Push/Back. Swift
How to Get Rgb Components from Color in Swiftui
Is There a Zip Function to Create Tuples with More Than 2 Elements
Using Foreach Loop with Binding Causes Index Out of Range When Array Shrinks (Swiftui)
Create a Paginated PDF-MAC Os X
What Are "Intervals" in Swift Ranges
Scanning Real-World Object and Generating 3D Mesh from It
Alamofire - Nsurlcache Is Not Working
What Does the Swiftui '@State' Keyword Do
Aws Cognito Swift Credentials Provider "Logins Is Deprecated: Use Awsidentityprovidermanager"