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

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)
}
}

Related

SwiftUI passing selected date from modal to parent variables

Need help with this please.
I have a view with 2 date variables and I want to show a modal which have the datepicker and let user pick different dates for these variables.
Currently I have two buttons that show the same sheet but pass different variable to the modal.
The problem the variable don’t update after dismissing the modal.
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerPresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerPresented)
{
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}
import SwiftUI
struct DatePickView: View {
#Environment(\.dismiss) private var dismiss
#Binding var selectDate: Date
var body: some View {
VStack(alignment: .center, spacing: 20) {
HStack {
Text("\(selectDate)")
.padding()
Spacer()
Button {
dismiss()
} label: {
Image(systemName: "delete.backward.fill")
.foregroundColor(.indigo)
}
}.padding()
DatePicker("", selection: $selectDate)
.datePickerStyle(.graphical)
}
}
}
First of all, thank you for your minimal, reproducible example: it is clear and can be immediately used for debugging. Answering to your question:
The problem with your code is that you have only one variable that opens the sheet for both dates. Even though you are correctly passing the two different #Bindings, when you toggle isDatepickerPresented you are asking SwiftUI to show both sheets, but this will never happen. Without knowing, you are always triggering the first of the sheet presentations - the one that binds secOneDate. The sheet that binds secTwoDate is never shown because you can't have two sheets simultaneously.
With that understanding, the solution is simple: use two different trigger variables. Here's the code corrected (DatePickView doesn't change):
struct Example: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerOnePresented = false
#State private var isDatepickerTwoPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerOnePresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerOnePresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerTwoPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerTwoPresented) {
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}

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

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.

Change (toggle) property on passed object [closed]

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 1 year ago.
Improve this question
I'm clearly not understanding this in SwiftUI.
I have a task struct with several properties one of which - 'isHotList' is a Boolean that I want to be able to toggle on and off.
I created a row view for each task and then use this to populate a list view for all the tasks loaded from JSON - no problems there. Moved on and added a detail view (which takes a task object passed in from the list view) with a toggle button for the isHotList the problem is that the toggle works correctly in the detail view but doesn't carry back to the list view .... ?
I suspect I've created two seperate instances of the task but I'm not sure. I've used #State to declare the task but I can't get #Binding to work correctly for me.
Toggle in the detail view, task is declared with #State in this view file ...
Button(action: {
task.isHotList.toggle()
print(task.isHotList)
}){
Image(systemName: "flame.fill")
.foregroundColor(task.isHotList ? Color.orange : Color.gray)
}
The list view (separate file)
ForEach(filteredHotTasks) { task in
NavigationLink(destination: TaskDetail(task: task)) {
TaskRow(task: task)
}
}
I'm really trying to learn here but this has me confused as to what is / isn't going on
As requested the full detail view file
struct TaskDetail: View {
#EnvironmentObject var modelData: ModelData
#State var task: Task
var taskIndex: Int {
modelData.tasks.firstIndex(where: {$0.id == task.id})!
}
var body: some View {
ScrollView {
VStack(alignment: .leading) {
HStack {
Text(task.title)
.font(.title2)
.padding(.bottom, 10)
Spacer()
Button(action: {
task.isHotList.toggle()
print(task.isHotList)
}){
Image(systemName: "flame.fill")
.foregroundColor(task.isHotList ? Color.orange : Color.gray)
}
}
HStack {
Text(task.category)
.font(.subheadline)
Spacer()
HStack {
Image(systemName: "calendar.badge.clock")
Text(task.dueDate)
.font(.subheadline)
}
}
}
.padding()
}
}
}
Remove #State
struct TaskDetail: View {
#EnvironmentObject var modelData: ModelData
var task: Task /* NEW */
var taskIndex: Int {
modelData.tasks.firstIndex(where: {$0.id == task.id})!
}
}
And use
Button(action: {
/* filteredHotTasks is the array that contains tasks. */
modelData.filteredHotTasks[taskIndex].isHotList.toggle()
print(task.isHotList)
})
Remember to pass modelData using .environmentObject(modelData) somwhere in one of your root views.
If you do not do, use #ObservedObject instead of #EnvironmentObject.
struct TaskDetail: View {
#ObservedObject var modelData: ModelData /* NEW */
var task: Task /* NEW */
var taskIndex: Int {
modelData.tasks.firstIndex(where: {$0.id == task.id})!
}
}
And
NavigationLink(destination: TaskDetail(modelData: modelData, task: task)) {
TaskRow(task: task)
}
Have a look at my answer to this question
Declaring TextField and Toggle in a SwiftUI dynamic form
For more info, read this article.
Managing Model Data in Your App
🎁
Confirm Task to Equatable in addition to the rest of the protocols you confirm to.
struct Task: Codable, Identifiable, Equatable {
}
Doing so you avoid using task.id whenever you want to know if two tasks are equal as in the firstIndex
var taskIndex: Int {
modelData.tasks.firstIndex(where: {$0 == task})!
}

SwiftUI List column didn't fit cell view after using navigationLink back

I create a List to show my player information. but my List column didn't fit my cell view. The textfield that disappear in some column. This is strange because most of they has "-1" number. Is somebody know any reason may cause it happen? Demo video: https://youtu.be/vUo9zZZ5olo
struct SelectPlayerCellView: View {
#ObservedObject var player: PlayerGameData
#State var newuniformNumber: Int = 0
var body: some View {
HStack{
Button(action: {
self.player.isPlayer.toggle()
}){
if self.player.isPlayer{
Image(systemName: "checkmark.rectangle")
}
else{
Image(systemName: "rectangle")
}
}
TextField("\(player.uniformNumber)", value: $newuniformNumber, formatter: NumberFormatter())
.font(.system(size: 40))
.frame(width:55)
.padding(5)
Text(player.name)
}
.padding(.horizontal,8)
.cornerRadius(20)
}
}
I put my cell view in the list
struct SelectPlayerView: View {
#EnvironmentObject var teamResult : TeamResult
#ObservedObject var allPlayerList : PlayersGameData = PlayersGameData()
#State var goToNextPage : Bool = false
var selectedPlayerList : PlayersGameData = PlayersGameData()
var body: some View {
VStack {
List{
ForEach(allPlayerList.playersGameDataArray, id: \.userId) { (player) in
SelectPlayerCellView(player: player)
}.onMove(perform: move)
}
NavigationLink(destination: SelectStartingLineupView(selectedPlayerList: self.selectedPlayerList).environmentObject(teamResult),isActive: $goToNextPage){
EmptyView()
}
VStack (spacing: 10){
Button(action: {
self.createPlayersList()
self.goToNextPage.toggle()
}){
Image(systemName: "arrowshape.turn.up.right.circle")
}
}
}.onAppear(perform: getTeamMemberResults)
.onDisappear(perform: clearTeamMemberResults)
.navigationBarItems(trailing: EditButton())
}

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)
}
}