SwiftUI : difficulty with dictionary to define a result list of search - xcode

I'll try to do one Search View.
I would like to save the result in a dictionary in order to create a list of result but xCode show me this error :
Cannot assign through subscript: 'self' is immutable
My code :
import SwiftUI
struct SearchListView: View {
#State var search: String = "test"
var stringDictionary: Dictionary = [Int: String]()
var body: some View
{
NavigationView
{
ForEach(chapterData) { chapter in
ForEach(chapter.lines) { line in
HStack {
if (self.search == line.text) {
HStack {
stringDictionary[0] = line.text
}
}
}
}
}
}
}
}
struct SearchListView_Previews: PreviewProvider {
static var previews: some View {
SearchListView(search: "test")
}
}
struct Chapter: Codable, Identifiable {
let id:Int
let lines: [Line]
}
struct Line: Codable, Identifiable {
let id: Int
let text: String
}

Related

Populating SwiftUI List with array elements that can be editied in TextEditor

I have a SwiftUI app that produces a List made from elements of an array of columns held in a struct.
I need the items in the row to be editable so I'm trying to use TextEditor but the bindings are proving difficult. I have a working prototype however the TextEditors are uneditable - I get the warning:
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.
Here's a much shortened version of my code which produces the same problem:
import SwiftUI
struct Item: Identifiable {
#State var stringValue: String
var id: UUID = UUID()
}
struct ArrayContainer {
var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
}
struct ContentView: View {
#State var wrapperArray: ArrayContainer = ArrayContainer()
var body: some View {
List {
Section(header: Text("Test List")) {
ForEach (Array(wrapperArray.items.enumerated()), id: \.offset) { index, item in
TextEditor(text: item.$stringValue)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
How can I bind the TextEditor to the items stringValues within the items array?
TIA.
#State should only be used as a property wrapper on your View -- not on your model.
You can use a binding within ForEach using the $ syntax to get an editable version of the item.
struct Item: Identifiable {
var stringValue: String
var id: UUID = UUID()
}
struct ArrayContainer {
var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
}
struct ContentView: View {
#State var wrapperArray: ArrayContainer = ArrayContainer()
var body: some View {
List {
Section(header: Text("Test List")) {
ForEach ($wrapperArray.items, id: \.id) { $item in
TextEditor(text: $item.stringValue)
}
}
}
}
}
This could be simplified further to avoid the ArrayContainer if you want:
struct ContentView: View {
#State var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
var body: some View {
List {
Section(header: Text("Test List")) {
ForEach ($items, id: \.id) { $item in
TextEditor(text: $item.stringValue)
}
}
}
}
}

How to make navigation link to expandable list in SwiftUI

I am trying to make navigation links to an expandable list.
I want to make navigation links only to sub-lists like "UICollectionView", "UIScrollView", "NavigationView", and "Expanding Rows".
But I don't know how to deal with this problem.
If someone helped me, I would appreciate it.
import SwiftUI
struct TutorialItem: Identifiable {
let id = UUID()
let title: String
var tutorialItems: [TutorialItem]?
}
struct ContentView: View {
var body: some View {
let tutorialItems: [TutorialItem] = [sampleUIKit(), sampleSwiftUI()]
List(tutorialItems, children: \.tutorialItems){
tutorial in
Text(tutorial.title)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
func sampleUIKit() -> TutorialItem {
return .init(title: "UIKit", tutorialItems:
[.init(title: "UICollectionView"),
.init(title: "UIScrollView")])
}
func sampleSwiftUI() -> TutorialItem {
return .init(title: "SwiftUI", tutorialItems:
[.init(title: "NavigationView"),
.init(title: "Expanding Rows")])
}
'''
I tried and below worked for me
#State var favItems = [BookmarkItem]()
var body: some View {
if #available(iOS 14.0, *) {
List {
ForEach(favItems) { item in
Section(header: Text(item.title)) {
OutlineGroup(
item.bookmarkItems ?? [],
id: \.id,
children: \.bookmarkItems
) { tree in
NavigationLink(destination: Text("-- \(tree.desc)")) {
Text("\(tree.desc)")
.font(.subheadline)
}
}
}
}
}.listStyle(SidebarListStyle())
} else {
// Fallback on earlier versions
}
}
//BookmarkItems
struct BookmarkItem: Identifiable {
var id = UUID()
var title: String
var desc: String
var bookmarkItems: [BookmarkItem]?
}

SwiftUI Picker with Cloud Firestore

I was wondering whether I was able to get some help on this one, I've been trying a while to get things working and functioning properly and have been able to pass the Firestore data into the picker view, but I'm unable to select the data to view in the 'selected' area. I have added my code and my Firestore setup.
Thanks in advance.
import SwiftUI
import Firebase
struct SchoolDetailsView: View {
#ObservedObject var schoolData = getSchoolData()
#State var selectedSchool: String!
var body: some View {
VStack {
Form {
Section {
Picker(selection: $selectedSchool, label: Text("School Name")) {
ForEach(self.schoolData.datas) {i in
Text(self.schoolData.datas.count != 0 ? i.name : "No Schools Available").tag(i.name)
}
}
Text("Selected School: \(selectedSchool)")
}
}.navigationBarTitle("Select your school")
}
}
}
struct SchoolPicker_Previews: PreviewProvider {
static var previews: some View {
SchoolDetailsView()
}
}
class getSchoolData : ObservableObject{
#Published var datas = [schoolName]()
init() {
let db = Firestore.firestore()
db.collection("School Name").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("Name") as! String
self.datas.append(schoolName(id: id, name: name))
}
}
}
}
struct schoolName : Identifiable {
var id : String
var name : String
}
Firestore Setup Image
To solve the issue with the code above the you can cast the tag to be the same type as the selectedSchool variable. This should then allow it to be selectable and is also safer as it uses optionals and allows the picker to be initially set to nil.
Example Code:
struct SchoolDetailsView: View {
#ObservedObject var schoolData = getSchoolData()
#State var selectedSchool: String?
var body: some View {
NavigationView {
VStack {
Form {
Section {
Picker(selection: $selectedSchool, label: Text("School Name")) {
ForEach(self.schoolData.datas.sorted(by: { $0.name < $1.name } )) {i in
Text(self.schoolData.datas.count != 0 ? i.name : "No Schools Available").tag(i.name as String?)
}
}
Text("Selected School: \(selectedSchool ?? "No School Selected")")
}
}.navigationBarTitle("Select your school")
}
}
}
}
As an alternative to the example above, you could also change the selectedSchool variable to be a schoolName type and cast the tag to be schoolName and this will also work. The only caveat with this approach is that the schoolName type must conform to the Hashable protocol.
Example Alternative Code:
struct schoolName: Identifiable, Hashable {
var id: String
var name: String
}
struct SchoolDetailsView: View {
#ObservedObject var schoolData = getSchoolData()
#State var selectedSchool: schoolName?
var body: some View {
NavigationView {
VStack {
Form {
Section {
Picker(selection: $selectedSchool, label: Text("School Name")) {
ForEach(self.schoolData.datas.sorted(by: { $0.name < $1.name } )) {i in
Text(self.schoolData.datas.count != 0 ? i.name : "No Schools Available").tag(i as schoolName?)
}
}
Text("Selected School: \(selectedSchool?.name ?? "No School Selected")")
}
}.navigationBarTitle("Select your school")
}
}
}
}
Either of these code examples should result in a working picker as follows:
Finally, as a side note for anyone when working with SwiftUI's default list view picker style, it must be enclosed within a NavigationView somewhere within the view hierarchy. This tripped me up when I first started using them :)

How to create Generic if #EnvironmentObject?

I've recently come across the need to write a Mock of a Class, as it causes the SwiftUI preview from working. Unfortunately, I get the error:
Property type 'T' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject'
In the View struct:
struct ContentView<T>: View {
#EnvironmentObject var mockFoobar: T
...
}
And also the error:
Type of expression is ambiguous without more context
For the Preview struct:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let mockFoobar: MockFoobar = MockFoobar()
return ContentView<MockFoobar>()
.environmentObject(mockFoobar)
}
}
The MockFoobar class is:
class MockFoobar: ObservableObject {
...
}
As the user #Asperi kindly provided, tested the following as suggested:
class Foobar: ObservableObject {
#Published var param: Bool = false
func start() {
self.param = true
}
}
struct MyFoobarView<T: ObservableObject>: View {
#EnvironmentObject var foobar: T
var body: some View {
VStack {
Text("Hello Foobar")
}
.onAppear {
self.foobar.start()
}
}
}
struct MyFoobarView_Previews: PreviewProvider {
static var previews: some View {
let foobar: Foobar = Foobar()
return MyFoobarView()
.environmentObject(foobar)
}
}
But I get the following errors (the first in the .onAppear and the second in the PreviewProvider):
Cannot call value of non-function type 'Binding<Subject>'
Generic parameter 'T' could not be inferred
The EnvironmentObject must be ObservableObject, so here is fix
struct ContentView<T: ObservableObject>: View {
#EnvironmentObject var mockFoobar: T
// .. other code here
Update: added demo with introduced model protocol
protocol Foobaring {
var param: Bool { get set }
func start()
}
class Foobar: ObservableObject, Foobaring {
#Published var param: Bool = false
func start() {
self.param = true
}
}
struct MyFoobarView<T: ObservableObject & Foobaring>: View {
#EnvironmentObject var foobar: T
var body: some View {
VStack {
Text("Hello Foobar")
}
.onAppear {
self.foobar.start()
}
}
}
struct MyFoobarView_Previews: PreviewProvider {
static var previews: some View {
let foobar: Foobar = Foobar()
return MyFoobarView<Foobar>()
.environmentObject(foobar)
}
}

SwiftUI Picker with Enum Source Is Not Enabled

I'm trying to understand the new SwiftUI picker style, especially with data from a source other than an array. I have built a picker with an enum. I first made a simple app with only the picker and associated enum. This works as expected.
Strangely, when I copy and paste that code into another app with other controls in the form, the picker seems to be inactive. I see it, but cannot click it.
Here's the first app (the picker works):
struct ContentView: View {
#State private var selectedVegetable = VegetableList.asparagus
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedVegetable, label: Text("My Vegetables")) {
ForEach(VegetableList.allCases) { v in
Text(v.name).tag(v)
//use of tag makes no difference
}
}
}
}
.navigationBarTitle("Picker with Enum")
}
}
}
enum VegetableList: CaseIterable, Hashable, Identifiable {
case asparagus
case celery
case shallots
case cucumbers
var name: String {
return "\(self)".map {
$0.isUppercase ? " \($0)" : "\($0)" }.joined().capitalized
}
var id: VegetableList {self}
}
Here's the app with other controls (picker does not work).
struct Order {
var includeMustard = false
var includeMayo = false
var quantity: Int = 1
var avocadoStyle = PepperoniStyle.sliced
var vegetableType = VegetableType.none
var breadType = BreadType.wheat
}
struct OrderForm: View {
#State private var order = Order()
#State private var comment = "No Comment"
#State private var selectedVegetable = VegetableType.asparagus
#State private var selectedBread = BreadType.rye
func submitOrder() {}
var body: some View {
Form {
Text("Vegetable Ideas")
.font(.title)
.foregroundColor(.green)
Section {
Picker(selection: $selectedVegetable, label: Text("Vegetables")) {
ForEach(VegetableType.allCases) { v in
Text(v.name).tag(v)
}
}
Picker(selection: $selectedBread, label: Text("Bread")) {
ForEach(BreadType.allCases) { b in
Text(b.name).tag(b)
}
}
}
Toggle(isOn: $order.includeMustard) {
Text("Include Mustard")
}
Toggle(isOn: $order.includeMayo) {
Text("Include Mayonaisse")
}
Stepper(value: $order.quantity, in: 1...10) {
Text("Quantity: \(order.quantity)")
}
TextField("Say What?", text: $comment)
Button(action: submitOrder) {
Text("Order")
}
}
.navigationBarTitle("Picker in Form")
.padding()
}
}
enum PepperoniStyle {
case sliced
case crushed
}
enum BreadType: CaseIterable, Hashable, Identifiable {
case wheat, white, rye, sourdough, seedfeast
var name: String { return "\(self)".capitalized }
var id: BreadType {self}
}
enum VegetableType: CaseIterable, Hashable, Identifiable {
case none
case asparagus
case celery
case shallots
case cucumbers
var name: String {
return "\(self)".map {
$0.isUppercase ? " \($0)" : "\($0)" }.joined().capitalized
}
var id: VegetableType {self}
}
Xcode 11 Beta 7, Catalina Beta 7
There is no behavior difference between Preview and Simulator .I must be missing
something simple here. Any guidance would be appreciated.
I wrapped the Form in a NavigationView and the pickers now operate as expected. I need to research that once the documentation is more complete but perhaps this can help someone else.

Resources