I've got the following object that contain list of strings:
class handler: ObservableObject {
#Published var items = [String]()
}
In some closure, I set this list with valid values :
for item in item_list! {
self.handler.items.append(item.stringData)
}
and in the ContentView part I've got Picker that support to present that list of strings in realtime:
VStack {
Picker("items", selection: $arg1) {
ForEach($handler.items, id: \.self) {
Text($0)
}
}
}
However, it fails to compile due to the following reason :
Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'
Any Idea how to resolve this ?
You don't need binding here to loop items, instead use handler directly (ie. without $), like
ForEach(handler.items, id: \.self) { // << here !!
Text($0)
}
Related
I am creating an autocomplete search TextField. Currently, it is a VStack of categories and I want the user to tap a category to select it, and have the category become the TextField value.
This code works, but it is just text and nothing happens on tap:
var body: some View {
...
Form {
...
ForEach(observed.results, id: \.id) {
Text($0.result).foregroundColor(.gray)
}
...
}
...
}
When I try to turn them into buttons, XCode throws a Trailing closure passed to parameter of type 'FormStyleConfiguration' that does not accept a closure error.
$search is the TextField's text attribute value.
#State private var search: String = ""
...
func setSearch(selected: String) {
self.search = selected
}
...
var body: some View {
...
Form {
...
ForEach(observed.results, id: \.id) {
Button( action: { self.setSearch(selected: $0.result) }) {
Text($0.result).foregroundColor(.gray)
}
}
...
}
...
}
How can I change state to update text field on tap?
Changing my Form to List produced more descriptive errors.
Used an explicit argument for my ForEach loop.
I intended $0 to be an implicit argument from my ForEach loop. However, when used within another closure, e.g. Button { } or .onTapGesture() { }, the nested closure threw an error believing the implicit argument to belong to itself.
I'm struggling to find how to bind an array value to a Toggle view in SwiftUI.
Lets says I have an observabled class with a Boolean array:
class TestClass: ObservabledObject {
#Published var onStates: [Bool] = [true, false, true]
static let shared = TestClass()
}
and in a View I have
...
Toggle(isOn: TestClass.shared.$onStates[0]) { // Throws error 'Referenceing subscript 'subscript(_:)' requires wrapped value of type '[Bool]'
Text("Example Toggle")
}
Why is it seemingly impossible to bind a particular array value to the toggle button?
Thanks.
We need observer in view for observable object, so fixed variant is
#StateObject var vm = TestClass.shared // << observer
var body: some View {
Toggle(isOn: $vm.onStates[0]) { // << binding via observer
Text("Example Toggle")
}
}
I'm learning SwiftUI at the moment. I've been playing around with loading a list from CoreData and making changes on / filtering etc. I've run into the issues below. Essentially as soon as I try to apply any conditionals within the ForEach I I'm presented with that error.
This works if I run iterate through the organisations in List itself rather than a ForEach. This isn't the ideal solution as I loose the inbuilt deletion function.
Am I missing something stupid?
let defaults = UserDefaults.standard
#EnvironmentObject var userData: UserData
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Organisation.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Organisation.name, ascending: true)])
var orgs: FetchedResults<Organisation>
var body: some View
{
NavigationView {
List {
ForEach(orgs, id: \.self) {org in
if !self.userData.showFavsOnly || org.isFavorite {
NavigationLink(destination: OrganisationView(org: org, moc: self.managedObjectContext)) {
OrganisationRow(org: org)
}
}
}
}
}
}
There error code I get is I get is on the for each line and is
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
Thanks for your help
Type '()' cannot conform to 'View'; only struct/enum/class types can
conform to protocols
This error means that your ForEach loop expects some View. But you give it an if-statement instead. What if the condition returns false?
The solution may be to wrap the if-statement in some View - it could be a Group, VStack, ZStack...
ForEach(orgs, id: \.self) { org in
Group {
if !self.userData.showFavsOnly || org.isFavorite {
NavigationLink(destination: OrganisationView(org: org, moc: self.managedObjectContext)) {
OrganisationRow(org: org)
}
}
}
}
I'm modelling view state in my viewModel using an enum...
enum ViewState<T> {
case idle
case error(Error)
case loading
case data([T])
I have a computed property to get the data
var data: [T] {
guard case let .data(data) = self else {
return []
}
return data
}
In one of my views I iterate through the data
var dropdownListView: some View {
ForEach(viewModel.state.data.indices, id: \.self) { index in
DropdownView(
viewModel: $viewModel.state.data[index],
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}.eraseToAnyView()
}
I get an error as you can't make a binding from a computed property so make my own custom binding...
ForEach(viewModel.state.data.indicies, id: \.self) { index in
DropdownView(viewModel: Binding<ItemViewModel>(
get: {return viewModel.state.data[index] },
set: { value in
var data = viewModel.state.data
data[index] = value
viewModel.state = .data(data)
},
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}
This works but are there any issues with setting the whole state again in the binding setter (I believe SwiftUI is intelligent enough that this would be efficient) or is there another way to do this here?
On my vision you mixed a state and a data, which are different things. So instead of .data([T]), I would recommend something like .loaded (ie, state) and keep data by standalone #Published var data: [T] property. If that adapted your code will look much more naturally.
Like
ForEach(viewModel.data.indices, id: \.self) { index in
DropdownView(
viewModel: $viewModel.data[index],
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}//.eraseToAnyView() // << you don't need this
}
I'm trying to make a list with the raw values of the cases from an enumeration with the new SwiftUI framework. However, I'm having a trouble with conforming the 'Data' to Identifiable protocol and I really cannot find information how to do it. It tells me "Initializer 'init(_:rowContent:)' requires that 'Data' conform to 'Identifiable'" The stub provides me with an ObjectIdentifier variable in the last extension, but don't know what should I return. Could you tell me how do it? How do I conform Data to Identifiable, so I can make a list with the raw values?
enum Data: String {
case firstCase = "First string"
case secondCase = "Second string"
case thirdCase = "Third string"
}
extension Data: CaseIterable {
static let randomSet = [Data.firstCase, Data.secondCase]
}
extension Data: Identifiable {
var id: ObjectIdentifier {
return //what?
}
}
//-------------------------ContentView------------------------
import SwiftUI
struct Lala: View {
var name: String
var body: some View {
Text(name)
}
}
struct ContentView: View {
var body: some View {
return List(Data.allCases) { i in
Lala(name: i.rawValue)
}
}
}
⚠️ Try not to use already used names like Data for your internal module. I will use MyEnum instead in this answer
When something conforms to Identifiable, it must return something that can be identified by that. So you should return something unique to that case. For String base enum, rawValue is the best option you have:
extension MyEnum: Identifiable {
var id: RawValue { rawValue }
}
Also, enums can usually be identified by their selves:
extension MyEnum: Identifiable {
var id: Self { self }
}
⚠️ Note 1: If you return something that is unstable, like UUID() or an index, this means you get a new object each time you get the object and this will kill reusability and can cause epic memory and layout process usage beside view management issues like transition management and etc.
Take a look at this weird animation for adding a new pet:
Note 2: From Swift 5.1, single-line closures don't need the return keyword.
Note 3: Try not to use globally known names like Data for your own types. At least use namespace for that like MyCustomNameSpace.Data
Inline mode
You can make any collection iterable inline by one of it's element's keypath:
For example to self:
List(MyEnum.allCases, id:\.self)
or to any other compatible keypath:
List(MyEnum.allCases, id:\.rawValue)
✅ The checklist of the identifier: (from WWDC21)
Exercise caution with random identifiers.
Use stable identifiers.
Ensure the uniqueness, one identifier per item.
Another approach with associated values would be to do something like this, where all the associated values are identifiable.
enum DataEntryType: Identifiable {
var id: String {
switch self {
case .thing1ThatIsIdentifiable(let thing1):
return thing1.id
case .thing2ThatIsIdentifiable(let thing2):
return thing2.id
}
}
case thing1ThatIsIdentifiable(AnIdentifiableObject)
case thing2ThatIsIdentifiable(AnotherIdentifiableObject)
You can try this way:
enum MyEnum: Identifiable {
case valu1, valu2
var id: Int {
get {
hashValue
}
}
}
Copyright © 2021 Mark Moeykens. All rights reserved. | #BigMtnStudio
Combine Mastery in SwiftUI book
enum InvalidAgeError: String, Error , Identifiable {
var id: String { rawValue }
case lessThanZero = "Cannot be less than zero"
case moreThanOneHundred = "Cannot be more than 100"
}