This was working until beta 7. It seems that when I want to pass one element in a collection as a binding to another view, the preview gets broken
I followed the release notes where it says #Binding no longer conforms to collection protocol comments are at the bottom
I have Catalina and xcode in beta 7
Parent:
struct SimpleStructure {
var label: String
}
struct ContentView: View {
#State private var myVariables = [SimpleStructure(label: "hello")]
var body: some View {
ChildView(myVariables: $myVariables)
}
}
Child
struct ChildView: View {
#Binding var myVariables: [SimpleStructure]
var body: some View {
List(myVariables.indexed(), id: \.1.label) {(index, variable) in
GrandChildView(myVariable: self.$myVariables[index])
}
}
}
GrandChild
struct GrandChildView: View {
#Binding var myVariable:SimpleStructure
var body: some View {
Text(myVariable.label)
}
}
UPDATE:
Still an issue with Beta 8
The release notes have a typo. You need to change:
var endIndex: Index { base.startIndex }
with
var endIndex: Index { base.endIndex }
Dunno, is this one solved for you? If not, maybe this helps:
struct ChildView: View {
#Binding var myVariables: [SimpleStructure]
var body: some View {
List(myVariables.indices) { index in
GrandChildView(myVariable: self.$myVariables[index])
}
}
}
If solved, either mark the correct answer, or provide a solution here by yourself in case you may have found one in the meantime.
The issue was fixed in SwiftUI Beta 10
Related
This is driving me crazy for many days now. I am trying to use the .fileImport() modifier in SwiftUI and apparently I am missing something VERY obvious but for the life of me I cannot find a way to have the "Select" option (see screenshots.
This is like the simplest it can get:
import SwiftUI
import UniformTypeIdentifiers
struct DocImporterView: View {
// #Binding var document: ProofOfBugDocument
#State var isPicking: Bool = false
var body: some View {
Button("Pick") {
isPicking.toggle()
}
.fileImporter(
isPresented: $isPicking,
allowedContentTypes: [.item, .folder, .directory],
allowsMultipleSelection: true, //this btw does not enable the select. Instead just starts the Document Picker in selection mode (but folders cannot be selected)
onCompletion: { result in
print("Picked: \(result)")
})
}
}
struct DocImporterView_Previews: PreviewProvider {
static var previews: some View {
DocImporterView()//(document: .constant(ProofOfBugDocument()))
}
}
Any ideas/help appreciated
I am trying to present a sequence of Views, each gathering some information from the user. When users enter all necessary data, they can move to next View. So far I have arrived at this (simplified) code, but I am unable to display the subview itself (see first line in MasterView VStack{}).
import SwiftUI
protocol DataEntry {
var entryComplete : Bool { get }
}
struct FirstSubView : View, DataEntry {
#State var entryComplete: Bool = false
var body: some View {
VStack{
Text("Gender")
Button("Male") {
entryComplete = true
}
Button("Female") {
entryComplete = true
}
}
}
}
struct SecondSubView : View, DataEntry {
var entryComplete: Bool {
return self.name != ""
}
#State private var name : String = ""
var body: some View {
Text("Age")
TextField("Your name", text: $name)
}
}
struct MasterView: View {
#State private var currentViewIndex = 0
let subview : [DataEntry] = [FirstSubView(), SecondSubView()]
var body: some View {
VStack{
//subview[currentViewIndex]
Text("Subview placeholder")
Spacer()
HStack {
Button("Prev"){
if currentViewIndex > 0 {
currentViewIndex -= 1
}
}.disabled(currentViewIndex == 0)
Spacer()
Button("Next"){
if (currentViewIndex < subview.count-1){
currentViewIndex += 1
}
}.disabled(!subview[currentViewIndex].entryComplete)
}
}
}
}
I do not want to use NavigationView for styling reasons. Can you please point me in the right direction how to solve this problem? Maybe a different approach?
One way to do this is with a Base View and a switch statement combined with an enum. This is a similar pattern I've used in the past to separate flows.
enum SubViewState {
case ViewOne, ViewTwo
}
The enum serves as a way to easily remember and track which views you have available.
struct BaseView: View {
#EnvironmentObject var subViewState: SubViewState = .ViewOne
var body: some View {
switch subViewState {
case ViewOne:
ViewOne()
case ViewTwo:
ViewTwo()
}
}
}
The base view is a Container for the view control. You will likely add a view model, which is recommended, and set the state value for your #EnvironmentObject or you'll get a null pointer exception. In this example I set it, but I'm not 100% sure if that syntax is correct as I don't have my IDE available.
struct SomeOtherView: View {
#EnvironmentObject var subViewState: SubViewState
var body: some View {
BaseView()
Button("Switch View") {
subViewState = .ViewTwo
}
}
}
This is just an example of using it. You can access your #EnvironmentObject from anywhere, even other views, as it's always available until disposed of. You can simply set a new value to it and it will update the BaseView() that is being shown here. You can use the same principle in your code, using logic, to determine the view to be shown and simply set its value and it will update.
I looked everywhere for a solution to my problem, but I couldn't find any. There's this question that
is similar, but I think I'm having a different problem here. So, my code (Xcode 12.1, developing for iOS 14.0) looks like this:
import SwiftUI
struct ContentView: View {
#ObservedObject var cm : FolderModel //Which is conformed to Codable, Identifiable, Equatable, ObservableObject
#ObservedObject var dm : DataManager //Which is conformed to Equatable, Identifiable, ObservableObject
#State var pressedFolder = false
#State var valview : ValuesView
NavigationView {
VStack{
ScrollView(.horizontal) {
HStack { ForEach(dm.storageFolders) { foldersForEach in
Button(action: {
valview = ValuesView(cm: foldersForEach, dm: dm)
pressedFolder = true
}, label: {
Text(foldersForEach.folderName)})
}
if pressedFolder == false {
Form {
ForEach(dm.values) { passwordDelForEach in
NavigationLink(//This works correctly)
}
}
} else if pressedFolder == true {
valview //This is the thing that it's not updating when values are added to the folders
}
}
struct ValuesView : View {
#ObservedObject var cm : FolderModel //Which is conformed to Codable, Identifiable, Equatable, ObservableObject
#ObservedObject var dm : DataManager //Which is conformed to Equatable, Identifiable, ObservableObject
var body : some View {
Form {
ForEach (cm.folderValues) { folderValuesForEach in
NavigationLink(//This works correctly)
}
}
}
}
The arrays into the DataManager are all declared like this:
#Published var storage : [StorageModel] = [] {
didSet {
objectWillChange.send()
}
}
typealias Storage = [StorageModel]
If I add anything into the arrays (from another View), data is stored correctly because by opening the .plist file (that the DataManager creates) I can see it gets correctly updated. Plus, every Button that I use has either the func of the DataManager save() (which has objectWillChange.send() within it) or I manually add dm.objectWillChange.send() to the action of the Button.
Despite all this, the things into the ForEach don't update. I only see the things that were there the first time I open the app, and to see the changes I have to close the app and reopen it.
What am I doing wrong?
Thanks to everyone who will answer!
I am seeing the same thing, only as of using the 14.2 simulator. I'm still trying to figure it out, but it seems like views inside of a ForEach are not properly re-rendered on data change.
So in ContentView, I've created a view with the following:
ViewName()
I'd like to change a variable in ContentView to the value of a variable in ViewName. I was hoping I could do something like:
ViewName() {
contentViewVariable = self.variableNameInViewNameInstance
}
but that was just kind of a guess as to how to access the value; it didn't work. Any suggestions would be greatly appreciated!
You can use #State and #Binding to achieve that. You should watch these WWDC videos in 2019 to learn more about this.
wwdc 2019 204 - introduction to swiftui
wwdc 2019 216 - swiftui essentials
wwdc 2019 226 - data flow through swiftui
struct ContentView: View {
#State private var variable: String
var body: some View {
ViewName($variable)
}
}
struct ViewName: View {
#Binding var variableInViewName: String
init(variable: Binding<String>) {
_variableInViewName = variable
}
doSomething() {
// updates variableInViewName and also variable in ContentView
self.variableInViewName = newValue
}
}
For whatever reason it is needed technically it is possible to do via callback closure.
Caution: the action in such callback should not lead to refresh sender view, otherwise it would be just either cycle or value lost
Here is a demo of usage & solution. Tested with Xcode 11.4 / iOS 13.4
ViewName { sender in
print("Use value: \(sender.vm.text)")
}
and
struct ViewName: View {
#ObservedObject var vm = ViewNameViewModel()
var callback: ((ViewName) -> Void)? = nil // << declare
var body: some View {
HStack {
TextField("Enter:", text: $vm.text)
}.onReceive(vm.$text) { _ in
if let callback = self.callback {
callback(self) // << demo of usage
}
}
}
}
class ViewNameViewModel: ObservableObject {
#Published var text: String = ""
}
After upgrading to Xcode 11 Beta 4, I starting seeing an error when using String(format: , args) with #State property. See code below. Second Text line throws an error:
Expression type 'String' is ambiguous without more context
while Texts 1, 3, and 4 work just fine.
struct ContentView : View {
#State var selection = 2
var body: some View {
VStack {
Text("My selection \(selection)") // works
Text("My selection \(String(format: "%02d", selection))") // error
Text("My selection \(String(format: "%02d", Int(selection)))") // works
Text("My selection \(String(format: "%02d", $selection.binding.value))") // works
}
}
}
I realize this is Beta software, but was curious if anyone can see a reason for this behavior or is this simply a bug. If this can't be explained, I'll file a radar.
In beta 4, the property wrapper implementation changed slightly. In beta 3, your View was rewritten by the compiler as:
internal struct ContentView : View {
#State internal var selection: Int { get nonmutating set }
internal var $selection: Binding<Int> { get }
#_hasInitialValue private var $$selection: State<Int>
internal var body: some View { get }
internal init(selection: Int = 2)
internal init()
internal typealias Body = some View
}
while on Beta 4, it does this:
internal struct ContentView : View {
#State #_projectedValueProperty($selection) internal var selection: Int { get nonmutating set }
internal var $selection: Binding<Int> { get }
#_hasInitialValue private var _selection: State<Int>
internal var body: some View { get }
internal init(selection: Int = 2)
internal init()
internal typealias Body = some View
}
Now I am guessing: this change makes it more difficult for the compiler to infer the type of your variable? Note that another alternative that does work, is helping the compiler a little, by casting selection as Int:
Text("My selection \(String(format: "%02d", selection as Int))")
Update (Xcode 11.2)
I also get the error:
'inout Path' is not convertible to '#lvalue Path'
with this code:
struct ContentView : View {
#State var selection = 2
var body: some View {
VStack {
Text(String(format: "%d", selection)) // does not work
}
}
}
Solved by and adding the $ prefix and then accessing wrappedValue in String(format:, args:):
struct ContentView : View {
#State var selection = 2
var body: some View {
VStack {
Text(String(format: "%d", $selection.wrappedValue)) // works
}
}
}