I have the code below:
struct ContentView: View {
#State var ex1 = "ABC"
#State var ex2 = "ABC"
#State var ex3 = "123"
var body: some View {
Button(action: {
ex1 = "cba" //This makes the button not disabled anymore
}) {
Text("toggle button not disabled")
}
Button(action: {
ex3 = "321" //this does nothing
}) {
Text("Changing this does nothing")
}
Button(action: {
ex3 = "123" //this does nothing
}) {
Text("this puts ex3 back to original")
}
Button(action: {
//actions
}) {
Text("Test button")
}
.disabled(ex1 == ex2 || ex3 == "123")
}
}
The problem is; As soon as ex1 doesn't equal ex2, the button enables. Even if ex3 still equals 123.
-Pressing the top button fulfills the first condition
-pressing the middle button fulfills the second condition
that should make the button able to get clicked, but the second condition doesn't do anything. All that has to work is condition 1.
Related
I have a view
struct Services: View {
#State private var isFirstSheetOpen = false
#State private var isSecondSheetOpen = false
var body: some View {
Button("Open sheet") {
isFirstSheetOpen.toggle() // turns true
}.sheet(isPresented: $isFirstSheetOpen) {
Button("Open second sheet") {
isFirstSheetOpen.toggle() // turns false
isFirstSecondOpen.toggle() // turns true
}.sheet(isPresented: $isSecondSheetOpen) {
Text("Second sheet")
}
}
}
}
I want to achieve something like Telegram has.
When opening the second sheet the first one should close (with animation).
https://s4.gifyu.com/images/IMG_8720.gif
I have two problems with my code.
If I put sheets nested (like in the example above) it closes the first one, then again opens it, even before opening the second sheet.
If I put sheets like this
// cut
Button() {
}.shet() { /*...*/ }
.shet() { /*...*/ }
// cut
It replaces the sheets immediately. If I wrap it inside
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
isSecondSheetOpen = true
}
animation takes too long (event with a small delay).
Could you help me to achieve exactly the same animation as shown in Gif?
You can use sheet(item:) instead of isPresented, which will close a previous sheet and open a new one when the item changes:
struct SheetContent: Identifiable, Hashable {
var id = UUID()
var text: String
}
struct ContentView: View {
#State var content: [SheetContent] = [.init(text: "1"), .init(text: "2"), .init(text: "3")]
#State var presented: SheetContent?
var body: some View {
Button("Open") {
presented = content.first
}
.sheet(item: $presented) { item in
Text(item.text)
Button("New sheet") {
presented = content.filter { $0.id != presented?.id }.randomElement()
}
}
}
}
I want to better understand binding data across view, so I made this demo app
First View - if isShowing is true, navigating to SecondView (binding value)
struct ParentView: View {
#State var isShowing = false
#State var value = 5
var body: some View {
NavigationView {
if value != 5 {
ThirdView(isShowing: $isShowing)
} else {
NavigationLink(isActive: $isShowing) {
SecondView(value: $value)
} label: {
Text("Go to second view")
}
}
}
}
}
Second view - updating ParentView value
struct SecondView: View {
#Binding var value: Int
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack {
Button {
value = 5
presentationMode.wrappedValue.dismiss()
} label: {
Text("Return 5")
}
Button {
value = 1
presentationMode.wrappedValue.dismiss()
} label: {
Text("Return 1")
}
}
}
}
ThirdView - showing in FirstView in case value is not 5
struct ThirdView: View {
#Binding var isShowing: Bool
var body: some View {
ZStack {
Button {
isShowing.toggle()
} label: {
Text("Its a problem... Go to second view")
}
}
}
}
I tried to toggle isShowing in ThirdView so it can open SecondView to update value again.
But when button is clicked in ThirdView, it doesnt do anything.
The way you have things set up, it won't change. When value != 5, your `NavigationLink does not exist in the view. Instead, you want to trigger it programmatically like this:
struct ParentView: View {
#State var isShowing = false
#State var value = 5
var body: some View {
NavigationView {
VStack {
Text(value.description)
if value != 5 {
ThirdView(isShowing: $isShowing)
} else {
// Change out the NavigationLink for a button that sets isShowing.
Button {
isShowing = true
} label: {
Text("Go to second view")
}
}
}
// By placing it in the background, it is always available to be triggered.
.background(
NavigationLink(isActive: $isShowing) {
SecondView(value: $value)
} label: {
EmptyView()
}
)
}
}
}
Lastly, you don't need to toggle isShowing in ThirdView. You are better off either dismissing the view or setting the value to false. Otherwise, you can get confused what it is doing when you are in your various views.
I have a view that displays two calculated strings. At present, I calculate the strings with .onAppear. But the view does not render until the strings are calculated, leaving the user watching the previous view for 2 to 5 seconds till the calculation is done, and the progress bar never gets shown.
The code is:
struct CalculatingProgressView: View {
var body: some View {
ProgressView {
Text("Calculating")
.font(.title)
}
}
}
struct OffspringView: View {
#State private var males: String = ""
#State private var females: String = ""
#State private var busy = true
func determineOffspring() {
let temp = theOffspring(of: sire, and: dam)
males = temp.0
females = temp.1
busy = false
}
var body: some View {
Section(header: Text("Male Offspring")) {
Text(males)
.font(.callout)
}
if busy {
CalculatingProgressView()
}
Section(header: Text("Female Offspring")) {
Text(females)
.font(.callout)
}
.onAppear { determineOffspring() }
}
}
How can I get the view to render with a progress bar so the user knows that the app is actually doing something?
your code seems to work for me. You could try this approach,
to show the CalculatingProgressView while it's calculating determineOffspring.
var body: some View {
if busy {
CalculatingProgressView()
.onAppear { determineOffspring() }
} else {
Section(header: Text("Male Offspring")) {
Text(males).font(.callout)
}
Section(header: Text("Female Offspring")) {
Text(females).font(.callout)
}
}
}
}
Note, your theOffspring(...) in determineOffspring should use a completion closure something like
the following, to "wait" until the calculations are finished:
func determineOffspring() {
theOffspring(of: sire, and: dam) { result in
males = result.0
females = result.1
busy = false
}
}
I probably worked too much today..
In this tiny app when the button is clicked a new view must appear after 3 seconds hVideoURL variable is assigned a value (which is not NIL). So a new view (sheet) should appear with the text "IS NOT NIL"..
But for some reason when it's shown i see "wow, it's nil", which means variable still has no value.
Why i that? What am i missing?
struct ContentView: View {
#State var hVideoURL: URL? = nil
#State var isPaused: Bool = false
var body: some View {
Button("Let's Go!") {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("settings isPaused to TRUE")
self.hVideoURL = URL(string: "https://firebasestorage.googleapis.com/v0/b/fitma-e3043.appspot.com/o/flamelink%2Fmedia%2F1-horizontal.mov?alt=media&token=8f7dfc0f-0261-4a78-9eb0-6154ce1d9dfe")
print("[debug] hVideoURL = \(hVideoURL)")
self.isPaused = true
}
}
.sheet(isPresented: self.$isPaused, onDismiss: {
self.isPaused = false
print("resume playing main video")
}) {
detailedVideoView
}
}
#ViewBuilder
var detailedVideoView: some View {
if self.hVideoURL != nil {
VStack {
Text("IS NOT NIL")
}
} else {
Text("wow, it's nil")
}
}
}
Is it possible to depend on multiple conditions in SwiftUI? For example to show a sheet:
.sheet(isPresented: $stateA && $stateB, content: { ... }) // this is not working
Or is a different approach known?
no, it is not possible! isPresented accept Binding, that means the state is updated if sheet will be dismissed. Which of stateA, stateB have to be changed? or both of them? Even though someone will try to define && operator where left and right side is Binding, that is very bad idea. Don't try to do it!
Move the logic to your model, better outside of any View.
UPDATE (for Asperi)
this is valid code (with your extension)
struct ContentView: View {
#State private var isFirst = true
#State private var isSecond = false
var body: some View {
VStack {
Button("TestIt") {
self.isSecond = true
}
.sheet(isPresented: $isFirst && $isSecond) {
Text("A")
}
}
}
}
Try it! Pressing TestIt will open the sheet. There is no Button to "go back", but you can dismiss it with well known gesture. And try to press TestIt again ...
"I can only show you the door..." (c) Morpheus
Today is a day of overloaded operators :^) - previous was here, here is for your case (tested with Xcode 11.3+)
extension Binding where Value == Bool {
static func &&(_ lhs: Binding<Bool>, _ rhs: Binding<Bool>) -> Binding<Bool> {
return Binding<Bool>( get: { lhs.wrappedValue && rhs.wrappedValue },
set: {_ in })
}
}
struct TestCustomBinding: View {
#State private var isFirst = true
#State private var isSecond = false
var body: some View {
VStack {
Button("TestIt") {
self.isSecond = true
}
.sheet(isPresented: $isFirst && $isSecond) {
Button("CloseMe") {
// sheet MUST be closed explicitly via one of states !
self.isSecond = false
}
}
}
}
}
It is possible to get different conditions from a variable.
struct ChangingButton: View {
var text: String
var onButton: String
var offButton: String
var changeButton: Bool
var buttonCondition: String {
if isOn {
return isOnImage
} else {
return isOffImage
}
}
var body: some View {
Button(action: {
action()
}
, label: {
VStack {
Image(systemName: buttonCondition)
Text(text)
}
})
}
}
struct ChangingButton_Previews: PreviewProvider {
static var previews: some View {
ChangingButton(text: "My Button", onButton: "on", offButton: "off", changeButton: true, action: {
}).background(Color.black)
}