SwiftUI ternary conditional bug? - swiftui

I've been trying to use the ternary operator in SwiftUI to make some view modifiers "dynamic" based on the value of a #State var I declared. But, seems like SwiftUI doesn't fully support this operator inside view modifiers. Is it a bug? What am I missing here?
struct Test: View {
#State var selectedIdx: Int?
var colors : [Color] = [.red, .orange, .yellow, .green, .blue]
var body: some View {
VStack {
//some other views that change "selectedIdx" (the #State var)
//...
ForEach(0..<colors.count) { idx in
Rectangle()
.frame(width: 70, height: 100)
//some other view modifiers that will be animated
.animation(Animation.easeInOut.delay(self. selectedIdx == nil ? 0.1*Double(self.colors.count - idx) : 0.1*Double(idx)))
}
}
}
}
The error disappears if I remove the ternary operator. I have tried different combinations for the ternary operator but none of them work:
.animation(Animation.easeInOut.delay(0.1*Double(self.selectedIdx == nil ? self.colors.count - idx : idx)))
.animation(Animation.easeInOut.delay(self.selectedIdx == nil ? 0.1*Double(self.colors.count - idx) : Double(idx)))
And even something simple like the following, doesn't work:
.animation(Animation.easeInOut.delay(self.selectedIdx == nil ? Double(idx) : Double(idx)))
Any ideas about the correct use of the ternary operator inside view modifiers?

Xcode 11.3.1 was buggy. Now that I updated to 11.4.1 it works fine.

Not sure where is the problem, but the following test code works here. Tested with Xcode 11.2 / iOS 13.2.
struct ContentView: View {
#State private var selectedIdx: Int?
var colors : [Color] = [.red, .orange, .yellow, .green, .blue]
var body: some View {
VStack {
//some other views that change "selectedIdx" (the #State var)
Button("Reset") { self.selectedIdx = nil }
//...
ForEach(0..<colors.count) { idx in
self.row(index: idx)
//some other view modifiers that will be animated
.animation(Animation.easeInOut.delay(self.selectedIdx == nil ? 0.1*Double(self.colors.count - idx) : 0.1*Double(idx)))
.onTapGesture { self.selectedIdx = idx }
}
}
}
func row(index: Int) -> some View {
Rectangle()
.fill(self.selectedIdx == index ? self.colors[index] : Color.black)
.frame(width: 70, height: 100)
}
}

Related

FocusState Textfield not working within toolbar ToolbarItem

Let me explain, I have a parent view with a SearchBarView, im passing down a focus state binding like this .
SearchBarView(searchText:$object.searchQuery, searching: $object.searching, focused: _searchIsFocused
That works perfectly as #FocusState var searchIsFocused: Bool is defined in parent view passing it down to the SearchBarView (child view ). In parent I can check the change in value and everything ok.
The problem relies when in parent I have the SearchBarView inside .toolbar {} and ToolBarItem(). nothing happens, not change in value of focus, etc. I have my SearchBarView in the top navigation bar and still want to use it there.. but I need to be able to know when it is in focus. if I use inside any VStack or whatever, everything perfectly..
-- EDIT --
providing more code to test
SearchBarView
struct SearchBarView: View {
#Environment(\.colorScheme) var colorScheme
#Binding var searchText: String
#Binding var searching: Bool
#FocusState var focused: Bool
var body: some View {
ZStack {
Rectangle()
.foregroundColor(colorScheme == .dark ? Color("darkSearchColor") : Color.white)
.overlay(
RoundedRectangle(cornerRadius: 13)
.stroke(.black.opacity(0.25), lineWidth: 1)
)
HStack {
Image(systemName: "magnifyingglass").foregroundColor( colorScheme == .dark ? .gray : .gray )
TextField("Search..", text: $searchText )
.focused($focused, equals: true)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
.disableAutocorrection(true).onSubmit {
let _ = print("Search textfield Submited by return button")
}
}
.foregroundColor(colorScheme == .dark ? Color("defaultGray") :.gray)
.padding(.leading, 13)
.padding(.trailing, 20).overlay(
HStack {
Spacer()
if searching {
ActivityIndicator().frame(width:15,height:15).aspectRatio(contentMode: .fit).padding(.trailing,15)
}
}
)
.onChange(of: focused) { searchIsFocused in
let _ = print("SEARCH IS FOCUSED VALUE: \(searchIsFocused) ")
}
}
.frame(height: 36)
.cornerRadius(13)
}
}
-- home View Code --
struct HomeView: View {
#Environment(\.colorScheme) var colorScheme
#FocusState var searchIsFocused: Bool
#State var searching:Bool = false
#State var searchQuery: String = ""
var body: some View {
NavigationStack {
GeometryReader { geofull in
ZStack(alignment: .bottom) {
Color("background")//.edgesIgnoringSafeArea([.all])
ScrollView(showsIndicators: false) {
VStack {
// Testing Bar inside VStack.. Here It Works. comment the bar the leave
// the one inside the .toolbar ToolbarItem to test
SearchBarView(searchText:$searchQuery, searching: $searching, focused: _searchIsFocused).padding(0)
}.toolbar {
//MARK: Navbar search field
ToolbarItem(placement:.principal) {
SearchBarView(searchText:$searchQuery, searching: $searching, focused: _searchIsFocused).padding(0)
}
}
.onChange(of: searchIsFocused) { searchIsFocused in
let _ = print("HOME VIEW searchIsFocused VALUE: \(searchIsFocused) ")
}
}
}
}
}
}
}

Extraneous Argument with Binding in SwiftUI

I was playing with binding in playground using SwiftUI, it's a very simple code
struct Car:View {
var kar = "🚚"
#Binding var startAnimation: Bool = false
var body: some View {
HStack {
Spacer()
Text(kar)
.font(.custom("Arial", size: 100))
.offset(x: self.startAnimation ? 0 - UIScreen.main.bounds.width + 100: 0)
}
}
}
But playground gave an error that says "Extraneous Argument Label ", I literally see no problem, especially when i code using Xcode. so is there a way to somehow hack it or did i miss something??
You can not or you should not give direct value to Binding, it bind data, you should use State for example like in code
with State:
import SwiftUI
struct CarView: View {
#State private var startAnimation: Bool = Bool()
var body: some View {
VStack {
Text("🚚")
.font(Font.system(size: 100))
.offset(x: startAnimation ? 50.0 - UIScreen.main.bounds.width/2 : UIScreen.main.bounds.width/2 - 50.0)
Button("Drive!") { startAnimation.toggle() }
.font(Font.headline)
.padding()
}
.animation(.easeIn(duration: 3.0), value: startAnimation)
}
}
With Binding:
import SwiftUI
struct ContentView: View {
#State private var startAnimation: Bool = Bool()
var body: some View {
VStack {
CarView(startAnimation: $startAnimation)
Button("Drive!") {
startAnimation.toggle()
}
.font(Font.headline)
.padding()
}
.animation(.easeIn(duration: 3.0), value: startAnimation)
}
}
struct CarView: View {
#Binding var startAnimation: Bool
var body: some View {
Text("🚚")
.font(Font.system(size: 100))
.offset(x: startAnimation ? 50.0 - UIScreen.main.bounds.width/2 : UIScreen.main.bounds.width/2 - 50.0)
}
}

SwiftUI - Update variable in ForEach Loop to alternate background color of a list

I tried to implement a pingpong variable for a list so I could alternate the background color. For some reason the below throws and error but the compiler just says "Failed to Build." When I remove the "switchBit" function call from within the view, it compiles fine. Can someone help me understand what I am doing wrong here?
struct HomeScreen: View {
let colors: [Color] = [.green,.white]
#State var pingPong: Int = 0
var body: some View {
NavigationView{
GeometryReader { geometry in
ScrollView(.vertical) {
VStack {
ForEach(jobPostingData){jobposting in
NavigationLink(destination: jobPostingPage()) {
JobListingsRow(jobposting: jobposting).foregroundColor(Color.black).background(self.colors[self.pingPong])
}
self.switchBit()
}
}
.frame(width: geometry.size.width)
}
}
.navigationBarTitle(Text("Current Listed Positons"))
}
}
func switchBit() {
self.pingPong = (self.pingPong == 1) ? 0 : 1
}
}
I guess you want alternate coloraturas for the rows. You will have to avoid the switchBit code and use something like below to switch colours:
struct Homescreen: View {
let colors: [Color] = [.green,.white]
#State var jobPostingData: [String] = ["1","2", "3","4"]
#State var pingPong: Int = 0
var body: some View {
NavigationView{
GeometryReader { geometry in
ScrollView(.vertical) {
VStack {
ForEach(self.jobPostingData.indices, id: \.self) { index in
JobListingsRow(jobposting: self.jobPostingData[index])
.foregroundColor(Color.black)
.background(index % 2 == 0 ? Color.green : Color.red)
}
}
.frame(width: geometry.size.width)
}
}
.navigationBarTitle(Text("Current Listed Positons"))
}
}
}

SwiftUI - Animation in view stops when scrolling the list

Considering the following code, why the animations in the views that are initialized without the n property stops when you scroll the list?
Tested on Xcode 11.3 (11C29) with a new default project on device and simulator.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
List(1...50, id: \.self) { n in
HStack {
KeepRolling()
Spacer()
KeepRolling(n: n)
}
}
}
}
}
struct KeepRolling: View {
#State var isAnimating = false
var n: Int? = nil
var body: some View {
Rectangle()
.frame(width: 50, height: 50)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0))
.onAppear {
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
self.isAnimating = true
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
IMO it is due to caching/reuse in List. For List all the values of KeepRolling() is the same, so .onAppear is not always re-called.
If to make every such view unique, say using .id as below, all works (tested with Xcode 11.2)
KeepRolling().id(UUID().uuidString)

Setting View visibility based on a property value?

When defining a view hierarchy using SwiftUI, is it possible to set the hidden() value of a View in the body of the definition?
For example:
var body: some View {
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
.hidden()
}
}
would hide the Text object, but I would like to use a boolean property to toggle visibility.
There is a way to do this using a ternary operator and the opacity value of the view, but I was hoping for a less clever solution.
If you don't want to use the opacity modifier this way:
struct ContentView: View {
#State private var showText = true
var body: some View {
VStack(alignment: .leading) {
Text("Hello world")
.font(.headline)
.opacity(showText ? 1 : 0)
}
}
}
you can decide to completely remove the view conditionally:
struct ContentView: View {
#State private var showText = true
var body: some View {
VStack(alignment: .leading) {
if showText {
Text("Hello world")
.font(.headline)
}
}
}
}
Consider that both ways are widely used in SwiftUI. For your specific case I'd honestly use the opacity modifier, but even the removal is fine.
Don't know if its still use useful because it's been a long time and I guess you found a solution since.But for anyone who's interested, we could create a modifier, which switches the visibility of the view according to a binding value :
import SwiftUI
struct IsVisibleModifier : ViewModifier{
var isVisible : Bool
// the transition will add a custom animation while displaying the
// view.
var transition : AnyTransition
func body(content: Content) -> some View {
ZStack{
if isVisible{
content
.transition(transition)
}
}
}
}
extension View {
func isVisible(
isVisible : Bool,
transition : AnyTransition = .scale
) -> some View{
modifier(
IsVisibleModifier(
isVisible: isVisible,
transition: transition
)
)
}
}
In use :
Text("Visible")
.isVisible(isVisible: isVisible)
.animation(.easeOut(duration: 0.3), value: isVisible)