SwiftUI Search bar - Please help my simplify my code [closed] - swiftui

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
again maybe a beginner question.
I've implemented a search function in my app. But I'm very unhappy with my code. I know I have to have to put my searchText into the .searchable() {...}
But also with help from different tutorials my knowledge isn't good enough to do this.
Could you please habe a look into my code and give me a hint so I could learn from your solution?
//
// FavoriteView.swift
// Blindzeln_Prototyp
//
// Created by Michael Ecke on 25.01.22.
//
import SwiftUI
struct FavoriteView: View {
#EnvironmentObject var blindzeln: BLINDzeln
#State private var searchText = ""
var body: some View {
NavigationView {
List{
ForEach(blindzeln.favorites, id: \.entryID) { item in
if searchText==""{
NavigationLink(destination: FavoriteDetailView(item: item)) {
VStack(alignment: .leading, spacing: 20.0) {
Text(item.title)
.font(.largeTitle)
.foregroundColor(.primary)
Text(item.textBody)
.font(.body)
.foregroundColor(.secondary)
.lineLimit(2)
BigDivider()
}
}.listRowSeparatorTint(.primary)
.listRowSeparator(.hidden)
}
else {
if item.title.localizedCaseInsensitiveContains(searchText) || item.textBody.localizedCaseInsensitiveContains(searchText){
NavigationLink(destination: FavoriteDetailView(item: item)) {
VStack(alignment: .leading, spacing: 20.0) {
Text(item.title)
.font(.largeTitle)
.foregroundColor(.primary)
Text(item.textBody)
.font(.body)
.foregroundColor(.secondary)
.lineLimit(2)
BigDivider()
}
}.listRowSeparatorTint(.primary)
.listRowSeparator(.hidden)
}
}
}
.onDelete(perform: delete)
.onMove(perform: onMove)
}
.searchable(text: $searchText) {}
```

Without the rest of the code to test, I can't be sure, but you should be able to condense your code like so:
struct FavoriteView: View {
#EnvironmentObject var blindzeln: BLINDzeln
#State private var searchText = ""
var body: some View {
NavigationView {
List{
ForEach(blindzeln.favorites.filter { searchText.isEmpty ||($0.title.localizedCaseInsensitiveContains(searchText) || $0.textBody.localizedCaseInsensitiveContains(searchText)) }, id: \.entryID){ item in
NavigationLink(destination: FavoriteDetailView(item: item)) {
VStack(alignment: .leading, spacing: 20.0) {
Text(item.title)
.font(.largeTitle)
.foregroundColor(.primary)
Text(item.textBody)
.font(.body)
.foregroundColor(.secondary)
.lineLimit(2)
BigDivider()
}
}.listRowSeparatorTint(.primary)
.listRowSeparator(.hidden)
}
}
.onDelete(perform: delete)
.onMove(perform: onMove)
}
.searchable(text: $searchText) {}
// Nothing changed past here...
}
}
Essentially the filter I set up is this:
if searchText is empty, return TRUE so the item is used;
if searchText is not empty, evaluate the other side of the OR which is 2 conditions with an OR. If either title OR textBody contains searchText, return TRUE so item is used;
if everything returns false, don't use item.
One last thing, rename your entryID in your model struct to id, make the model struct conform to Identifiable and then your ForEach (leaving out the .filter can be this:
ForEach(blindzeln.favorites) { item in
as an Identifiable struct does not need to use id: in a ForEach initializer.

Related

SwiftUI best way to create base view that will be inherited [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 months ago.
Improve this question
I try to find the best way to make a View that will be served like a base view with some default behavior. Here is a example of what I did the best so far:
struct BaseView <Content: View>: View{
#Binding var showSideMenu: Bool
let view: () -> Content
var body: some View {
VStack{
view()
}
.frame(maxWidth:.infinity, maxHeight: .infinity)
.offset(x: showSideMenu ? Screen.width / 1.5 : 0)
.disabled(showSideMenu ? true : false)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
}
}
struct MainView: View {
#Binding var showSideMenu: Bool
var body: some View {
BaseView(showSideMenu: $showSideMenu) {
VStack{
Text("HOME")
Button(action: {
withAnimation {
self.showSideMenu.toggle()
}
}) {
Text("Show Menu")
}
}
}
}
}
What is not good here BaseView must first implement VStack to be like a parent to the other View-s. I want to avoid that or some better solution for a problem.
How about using a custom View Modifier. It gets content passed in and you don't need extra stacks:
struct ContentView: View {
#State var showSideMenu: Bool = false
var body: some View {
VStack{
Text("HOME")
Button(action: {
withAnimation {
self.showSideMenu.toggle()
}
}) {
Text("Show Menu")
}
}
.baseViewModifier(showSideMenu: $showSideMenu)
}
}
extension View {
func baseViewModifier(showSideMenu: Binding<Bool>) -> some View {
self.modifier(BaseViewModifier(showSideMenu: showSideMenu))
}
}
struct BaseViewModifier: ViewModifier {
#Binding var showSideMenu: Bool
func body(content: Content) -> some View {
content
.frame(maxWidth:.infinity, maxHeight: .infinity)
.offset(x: showSideMenu ? UIScreen.main.bounds.width / 1.5 : 0)
.disabled(showSideMenu ? true : false)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
}
}

How to recreate the grid (in screenshot) in SwiftUI without breaking navigation?

I am trying to recreate a layout similar to the Reminders app. Looking at it makes me think it was built with SwiftUI. I also believe Apple mentioned so in one of the WWDC videos (can't remember which one).
This above screenshot seems to be a List, with a LazyVGrid as the first View inside the List. Tapping on each of the items in the LazyVGrid, such as Today, Scheduled, All and Flagged, navigates to the relevant screen, which means they are all NavigationLinks. Also note that the LazyVGrid has 2 columns.
And then there is another section "My Lists" which has rows which look like regular list rows in a List with style .insetGrouped. Also, every item in this Section is a NavigationItem, and thus comes with the disclosure indicator on the right as usual. Recreating this is trivial, so it has been left out from the MRE.
I am having trouble recreating the first section, which has that LazyVGrid. I faced 3 problems (as mentioned in the image), of which I have been able to solve the first one only. The other two problems remain. I want to know if this MRE can be fixed, or is my entire approach incorrect.
I am including a minimum reproducible example below.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
RemindersView()
}
}
}
struct RemindersView: View {
private var columns: [GridItem] = [GridItem(.adaptive(minimum: 150))]
private var smartLists: [SmartList] = SmartList.sampleLists
var body: some View {
NavigationView {
List {
Section(header: Text("Using LazyVGrid")) {
grid
}
Section(header: Text("Using HStack")) {
hstack
}
}
.navigationTitle("Store")
}
.preferredColorScheme(.dark)
}
private var grid: some View {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(smartLists) { smartList in
// This use of **ZStack with an EmptyView with opacity 0** is a hack being used to avoid the disclosure indicator on each item in the grid
ZStack(alignment: .leading) {
NavigationLink( destination: SmartListView(list: smartList)) {
EmptyView()
}
.opacity(0)
SmartListView(list: smartList)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
private var hstack: some View {
ScrollView(.horizontal) {
HStack {
ForEach(smartLists) { smartList in
NavigationLink(destination: SmartListView(list: smartList)) {
SmartListView(list: smartList)
}
.buttonStyle(.plain)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
}
struct RemindersView_Previews: PreviewProvider {
static var previews: some View {
RemindersView()
}
}
struct SmartList: Identifiable {
var id: UUID = UUID()
var title: String
var count: Int
var icon: String
var iconColor: Color
static var sampleLists: [SmartList] {
let today = SmartList(title: "Today", count: 5, icon: "20.circle.fill", iconColor: .blue)
let scheduled = SmartList(title: "Scheduled", count: 12, icon: "calendar.circle.fill", iconColor: .red)
let all = SmartList(title: "All", count: 77, icon: "tray.circle.fill", iconColor: .gray)
let flagged = SmartList(title: "Flagged", count: 5, icon: "flag.circle.fill", iconColor: .orange)
return [today, scheduled, all, flagged]
}
}
struct SmartListView: View {
var list: SmartList
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Image(systemName: list.icon)
.renderingMode(.original)
.font(.title)
.foregroundColor(list.iconColor)
Spacer()
Text("\(list.count)")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.horizontal, 8)
}
Text(list.title)
.font(.system(.headline, design: .rounded))
.foregroundColor(.secondary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.gray.opacity(0.25))
)
.padding(2)
.frame(minWidth: 150)
}
}
EDIT 1: Adding video demo of what editing the dynamic Grid looks like and how the Grid has dynamic grid items (via the Edit button at the top right): https://imgur.com/a/TV0kifY

Passing data across views for unique objects in a forEach loop in swift

I have two views, ViewAssignment and TaskDetailView. My ViewAssignment page fetches data from an environment object, and creates a list using the data.
Upon each item of the list being clicked on, the TaskDetailView pops in as a navigation link, however, I am having trouble making the information in the TaskDetailView unique to that particular iteration (the item in the list)
I believe the trouble comes from my TaskDetailView.swift
import SwiftUI
struct TaskDetailView: View {
#EnvironmentObject var assignment: Assignments
#State var taskNotes = ""
var body: some View {
VStack(spacing: 10) {
Image("english-essay")
.resizable()
.scaledToFit()
.frame(width: 250, height: 160)
.cornerRadius(20)
Text(self.assignment.data.first?.taskName ?? "Untitled Task")
.font(.title2)
.fontWeight(.semibold)
.lineLimit(2)
.multilineTextAlignment(.center)
HStack(spacing: 20) {
Label(self.assignment.data.first?.weighting ?? "0", systemImage: "percent")
.font(.subheadline)
.foregroundColor(.secondary)
Text(self.assignment.data.first?.dueDate ?? "No Date")
.font(.subheadline)
.foregroundColor(.secondary)
}
TextField("Write any notes here", text: $taskNotes)
.font(.body)
.padding()
Spacer()
}
}
}
struct TaskDetailView_Previews: PreviewProvider {
static var previews: some View {
TaskDetailView() // I assume there is some information I have to pass through here
}
}
For details, this is my other view:
import SwiftUI
struct ViewAssignment: View {
// Observed to update the UI
#EnvironmentObject var assignment: Assignments
var body: some View {
ZStack {
NavigationView {
List(self.assignment.data) { task in
NavigationLink (
destination: TaskDetailView(),
label: {
Image(systemName: "doc.append.fill")
.scaleEffect(2.5)
.padding()
VStack(alignment: .leading, spacing: 3) {
Text(task.taskName)
.fontWeight(.semibold)
.lineLimit(2)
Text(task.dueDate + " - " + task.subject)
.font(.subheadline)
.foregroundColor(.secondary)
}
})
}
.navigationTitle("My Tasks")
.listStyle(InsetGroupedListStyle())
}
}
}
}
struct ViewAssignment_Previews: PreviewProvider {
static var previews: some View {
ViewAssignment()
}
}
I would also like to know if, upon making the screen unique for each item in the list, would I be able to have the contents of the text field saved upon reloading the app, Perhaps through #AppStorage?
Thank you for the assistance.
If I understand correctly what you are trying to do:
a TaskDetailView displays the detail of a ... Task.
So you should have a Task structure like this:
struct Task {
let name: String
let subject: String
...
}
You have to create one (or more) instance of Task to test your TaskDetailView:
extension Task {
var test: Task {
Task(name: "Test", subject: "Test Subject")
}
}
Now in the preview of your TaskDetailView you can try to display your example :
struct TaskDetailView_Previews: PreviewProvider {
static var previews: some View {
TaskDetailView(task: Task.test) // here
}
}
For the moment nothing is happening. Because your TaskDetailView doesn't have a task parameter.
struct TaskDetailView: View {
var task: Task
var body: some View {
...
}
Now its body can use the different parameters of this Task.
Text(task.name)
.font(.title2)
.fontWeight(.semibold)
.lineLimit(2)
.multilineTextAlignment(.center)
Now in your List:
List(self.assignment.data) { task in
NavigationLink (
destination: TaskDetailView(task: task), // <- here !!!
label: {
Image(systemName: "doc.append.fill")
.scaleEffect(2.5)
.padding()
VStack(alignment: .leading, spacing: 3) {
Text(task.name)
.fontWeight(.semibold)
.lineLimit(2)
}
})
}

Navigation repeats itself several times after clicking the object

I just shared this Bug with Apple. I want to share with you.
Application Follow
1 - After the user logs on to the onBoardingView page, they are directed to ContentView with fullScreenCover.
2 - ContentView page contains objects in TabView that are repeated with ForEach. Clicking on these objects will take you to the DetailView page.
3 - However, Navigation repeats itself several times after clicking the object.
My English is bad. Sorry for this.
Video is here
Project file is here
struct OnboardView: View {
#State var isLogin: Bool = false
var body: some View {
Button(action: {self.isLogin = true}) {
Text("Login")
}
.fullScreenCover(isPresented: self.$isLogin) {
ContentView()
}
}
}
struct ContentView: View {
#State var selected: String = ""
var items: [String] = ["1","2","3","4","5","6","7","8","9","10"]
var body: some View {
NavigationView {
TabView(selection: $selected) {
ForEach(items, id: \.self) { item in
NavigationLink(
destination: DetailView(),
label: {
Text(item)
.foregroundColor(.white)
.padding()
.background(Color.orange)
.cornerRadius(10)
})
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
When working with ForEach in SwiftUI, you have to be extra careful on the ids.
Try changing items to items.indices instead:
ForEach(items.indices, id: \.self) { item in
NavigationLink(
destination: Text("Detail View"),
label: {
Text(items[item])
.foregroundColor(.white)
.padding()
.background(Color.orange)
.cornerRadius(10)
}
)
}

ScrollView acting weired (Xcode 11 GM seed - SwiftUI)

I was trying to make a custom list. And its acting weired if we add Encapsulated VStack in scrollView and try to add new row from that VStack. But we have to encapsulate because in Xcode will give "complex view complier error". I am providing full code for better understanding. Please try to run it. New element is not added as expected and its pushing everything upward.
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}}
struct ContentView : View {
#State var array: [String] = []
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
cView()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}}
When I reformatted and removed unused stuff I got:
struct RowView: View {
let text: String
var body: some View {
VStack{
HStack{
Spacer()
Text(text).foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(
action: { self.array.append("Test") },
label: { Text("Add") }
)
ForEach(array, id: \.self){text in
RowView(text: text)
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
struct ContentView : View {
var body: some View {
List {
VStack{
Text("d")
cView()
}
}
}
}
ScrollView is a real PITA and it hates Text which is why I replaced it with a List. RowView was missing a Text IMHO so I put one in. The array in ContentView was never used so I removed it, similarly a navigatinBarTitle needs a NavigationView.
This isn't really an answer as it uses List instead of ScrollView but it does point to where your problems lie. It is also very strange as every thing is in a single List row but I tried to change as little as possible.
You might like to try running SwiftLint on your code. I often swear at it, especially when it complains about the cyclomatic complexity of my enum switches but it does improve my code.
Most likely a bug, but I did not need to encapsulate. And if I don't, the code works as expected:
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct ContentView : View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}
}