Show DisclosureGroup view based on state - swift

I'm fairly new to SwiftUI, and I'm having trouble wrapping my head around the following issue I ran into. I have a button which toggles a state property, and I'd like to display a DisclosureGroup when the button's state is toggle on. For some reason, I can display any sort of view with my code below, with the exception of a DisclosureGroup:
#Binding var showing : Bool
#Binding var revealDetails : Bool
var body: some View {
if showing {
VStack {
DisclosureGroup("Monday", isExpanded: $revealDetails){
Text("7PM - 10PM").frame(height: 100)
}
.frame(width: 150)
.buttonStyle(PlainButtonStyle()).accentColor(.black)
}
}
}
}
The above code does not work when I present in my ContentView, however, the strange thing is, if I add some sort of empty view above the DisclosureGroup, it does work. So for now, I'm including a Text("") inside the VStack. Any thoughts on why this is?

I think you're not passing correct values to your bindings, i can tell you clearly after seeing your code in ContentView as you haven't attached it in the question but you can copy paste below code and customise it depending on your needs.
ContentView
import SwiftUI
struct ContentView: View {
// MARK: - PROPERTIES
#State private var showDiscloureGroup = false
#State private var showDetails = false
// MARK: - BODY
var body: some View {
VStack{
Toggle("Show Disclosure Group", isOn: $showDiscloureGroup)
Toggle("Show Details", isOn: $showDetails)
MyDiscloureGroup(showing: $showDiscloureGroup, revealDetails: $showDetails)
}//: VSTACK
.padding()
}
}
// MARK: - PREVIEW
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MyDiscloureGroupView
import SwiftUI
struct MyDiscloureGroupView: View {
#Binding var showing : Bool
#Binding var revealDetails : Bool
var body: some View {
if showing {
VStack {
DisclosureGroup("Monday", isExpanded: $revealDetails){
Text("7PM - 10PM").frame(height: 100)
}
.frame(width: 150)
.buttonStyle(PlainButtonStyle()).accentColor(.black)
}
}
}
}
struct MyDiscloureGroup_Previews: PreviewProvider {
static var previews: some View {
MyDiscloureGroupView(showing: .constant(true), revealDetails: .constant(true))
}
}

Related

SwiftUI | Preview not updating on #Binding var value change

I am learning SwiftUI and tried to make a simple todo list but I'm having issues understanding why #Binding property doesn't update my preview.
The code is the following.
import SwiftUI
struct TodoRow: View {
#Binding var todo: Todo
var body: some View {
HStack {
Button(action: {
todo.completed.toggle()
}, label: {
Image(systemName: todo.completed ? "checkmark.square" : "square")
})
.buttonStyle(.plain)
Text(todo.title)
.strikethrough(todo.completed)
}
}
}
struct TodoRow_Previews: PreviewProvider {
static var previews: some View {
TodoRow(todo: .constant(Todo.sampleData[0]))
}
}
The preview doesn't update when I click the square button but the app works fine. Am I using it incorrectly?
EDIT:
Even without .constant(#), the preview doesn't work.
struct TodoRow_Previews: PreviewProvider {
#State private static var todo = Todo.sampleData[0]
static var previews: some View {
TodoRow(todo: $todo)
}
}
Found a solution in the article Testing SwiftUI Bindings in Xcode Previews.
In order for previews to change you must create a container view that holds state and wraps the view you're working on.
In my case what I've ended up doing was changing my preview to the following.
struct TodoRow_Previews: PreviewProvider {
// A View that simply wraps the real view we're working on
// Its only purpose is to hold state
struct TodoRowContainer: View {
#State private var todo = Todo.sampleData[0]
var body: some View {
TodoRow(todo: $todo)
}
}
static var previews: some View {
Group {
TodoRow(todo: .constant(Todo.sampleData[0]))
.previewDisplayName("Immutable Row")
TodoRowContainer()
.previewDisplayName("Mutable Row")
}
}
}

TextField's .focused modifier not working when "out of view" SwiftUI

I'm working with a SwiftUI List with a TextEditor at the bottom of the List. The TextEditor becomes active or "Focused" when a button is pressed.
To achieve this, I have added a .focused modifier to the TextEditor. When the List is scrolled to the bottom and the TextEditor is visible this works as expected and shows the keyboard when the button is pressed.
But the problem is that when the List is scrolled to the top and the TextEditor is not visible on the screen the TextField doesn't seem to get focused.
import SwiftUI
struct ContentView: View {
var array = Array(0...20)
#State private var newItemText : String = ""
#FocusState var focused: Bool
var body: some View {
VStack{
List{
ForEach(array, id: \.self) {
Text("\($0)")
}
TextEditor(text: $newItemText)
.focused($focused)
}
Button {
focused = true
} label: {
Text("Add Item")
}
}
}
}
You have to set the field in a DispatchQueue.
import SwiftUI
struct ContentView: View {
var array = Array(0...20)
#State private var newItemText : String = ""
#FocusState var focusedField: Bool
var body: some View {
VStack {
ScrollViewReader { scrollView in
List {
ForEach(array, id: \.self) {
Text("\($0)")
}
TextField("", text: self.$newItemText)
.id("TextEditor")
.focused($focusedField)
}
Button(action: {
scrollView.scrollTo("TextEditor")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.focusedField = true
}
}) {
Text("Add Item")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Hope this solves your problem!

Trouble getting EnvironmentObject to update the UI

I originally posted another question asking this in the context of a project I was trying to develop, but I can't even get it to work in a vacuum so I figured I'd start with the basics. As the title suggests, my EnvironmentObjects don't update the UI as they should; in the following code, the user enters text on the ContentView and should be able to see that text in the next screen SecondView.
EDITED:
import SwiftUI
class NameClass: ObservableObject {
#Published var name = ""
}
struct ContentView: View {
#StateObject var myName = NameClass()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $myName.name)
NavigationLink(destination: SecondView()) {
Text("View2")
}
}
}.environmentObject(myName)
}
}
struct SecondView: View {
#EnvironmentObject var myself: NameClass
var body: some View {
Text("\(myself.name)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NameClass())
}
}
However, the SecondView doesn't show the text that the user has written, but the default value of name (blank). What am I doing wrong here?
class NameClass: ObservableObject {
#Published var name = ""
}
struct ContentView: View {
#StateObject var myName = NameClass()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $myName.name)
NavigationLink(destination: SecondView()) {
Text("View2")
}
}
}.environmentObject(myName)
}
}
struct SecondView: View {
#EnvironmentObject var myself: NameClass
var body: some View {
Text("\(myself.name)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NameClass())
}
}

Why doesn't the Text update when using #Binding?

The Working works as expected.
But using the #Binding in the NotWorking example, doesn't seem to update the Text control. Why doesn't the #Binding version work, what am I missing here?
Initial Launch:
After Typing:
struct Working: View {
//Binding from #State updates both controls
#State private var text = "working"
var body: some View {
VStack {
TextField("syncs to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct NotWorking: View {
//Using the #Binding only updates the TextField
#Binding var text: String
var body: some View {
//This does not works
VStack {
TextField("won't sync to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct Working_Previews: PreviewProvider {
#State static var text = "not working"
static var previews: some View {
VStack {
Working()
NotWorking(text: $text)
}
}
}
Static #States don't work. It's the fact that it being static means that the struct Working_Previews isn't mutated when text is changed, so it won't refresh.
We can test this by changing from a PreviewProvider to an actual View:
struct ContentView: View {
#State static var text = "not working"
var body: some View {
VStack {
Working()
NotWorking(text: ContentView.$text)
}
}
}
This code gives the following runtime message:
Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
Thanks to #George_E. I define #State in a wrapper view and display that for the preview. The WrapperView simply displays the control that I want to preview but it contains the State.
struct Working_Previews: PreviewProvider {
//Define the State in a wrapper view
struct WrapperView: View {
#State var text = "Preview is now working!!!"
var body: some View {
NotWorking(text: $text)
}
}
static var previews: some View {
VStack {
Working()
//Don't display the view that needs the #Binding
//NotWorking(text: $text)
//Use the WrapperView that has #State and displays the view I want to preview.
WrapperView()
}
}
}

Difference between toggle() and Toggle

I am learning Modals in SwiftUI and the code is below:
ContentView.swift:
import SwiftUI
struct ContentView: View {
#State private var showingAddUser = false
var body: some View {
return VStack {
Text("Modal View")
}.onTapGesture {
self.showingAddUser.toggle()
print(self.showingAddUser) //for console
}
.sheet(isPresented: $showingAddUser) {
Addview(isPresented: self.$showingAddUser)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddView.swift:
import SwiftUI
struct Addview: View {
#Binding var isPresented: Bool
var body: some View {
Button("Dismiss") {
self.isPresented = false
}
}
}
struct Addview_Previews: PreviewProvider {
static var previews: some View {
Addview(isPresented: .constant(false))
}
}
When I try to run the code for the first time and check the print output in console, boolean value changes to true however if I initialise #State variable showingAddUser with true the console output is unchanged that is it remains true. Should't toggle() flip the boolean value to false?
Is this toggle() different from Toggle switch from a concept point of view?
The toggle() is a mutating function on value type Bool. If you set the initial value of showingAddUser as true it will display the AddUser View when launched initially and it's not if set to false, that's the difference.
Toggle is a SwiftUI View. It can be used as any other View in SwiftUI body, like this:
struct ContentView: View {
#State var bool: Bool
var body: some View {
Toggle(isOn: $bool) {
Text("Hello world!")
}
}
}
There is no need for isPresented Boolean in Add View
Try This
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var showingAddUser = false
var body: some View {
return VStack {
Text("Modal View")
}.onTapGesture {
self.showingAddUser = true
print(self.showingAddUser) //for console
}
.sheet(isPresented: $showingAddUser) {
Addview()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddView.swift
import SwiftUI
struct AddView: View {
var body: some View {
Button(action:
// Do Your Things
) {
Text("MyButton")
}
}
}
struct Addview_Previews: PreviewProvider {
static var previews: some View {
Addview()
}