Experiencing a weird issue with my list. Looking at the screenshot provided below, my sub titles under "Eggs" all have a left margin issue, which pushes the sub headers away from there starting position. I don't think it's my padding causing this, honestly stumped, hoping you guys could help out.
Thanks!
struct FoodSearchResultsView: View {
#EnvironmentObject private var foodApi: FoodApiSearch
#EnvironmentObject var mealEntryObj: MealEntrys
//textfield input
#State private var searchResultsItem = ""
//if toggled, will display, binded to search bar
#Binding var userSearch: Bool
//when false, api results will not display
#Binding var isViewSearching:Bool //sending to searchboar
var body: some View {
if userSearch{
VStack{
Text(isViewSearching ? "Results" : "Searching..")
Spacer()
// delays showing api call
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
self.isViewSearching = true
}
}
//if user has completed searching for a food
if isViewSearching{
List(foodApi.userSearchResults){meal in
VStack{
HStack{
VStack(alignment: .leading){
Text(meal.mealName)
.padding(.leading,-50)
HStack{
Text(meal.calories + "cals, ")
.font(.caption)
.offset(y:8)
Text(meal.brand)
.font(.caption)
.offset(y:8)
}
.frame(maxWidth:150)
.padding(.leading,-50)
}
.foregroundColor(.black)
//Spacer()
Button(action: {
userSearch = false
isViewSearching = false //is user actively searching, communicates with journalEntryMain
//push meal to meal entry break fast
mealEntryObj.mealEntrysBreakfast.append(meal)
}){
Image(systemName: "plus.app")
.font(.title2)
.foregroundColor(.blue)
.offset(x: 70)
.frame(width:50)
}
}
}
.frame(width:220, height:40) //width of background
.padding([.leading, .trailing], 60)
.padding([.top, .bottom], 10)
.background(RoundedRectangle(
cornerRadius:20).fill(Color("LightWhite")))
.foregroundColor(.black)
}
.listStyle(.plain)
.listRowSeparator(.hidden)
//.frame(height:400)
}
}
}
}
}
Related
I have a view that generates multiple rows in a list. Each row - I append a button that creates a pop out of sorts over the row. My issue is since the button is being generated separately on each row, when I toggle another button on a different row, the previous button I opened remains open until I click it again.
Is there a way I can have the rows communicate with each other so if another button is clicked, the button previously clicked disappears?
struct RecipeFullListRow: View {
var body: some View {
ZStack{
Text("Cheese Omelette")
.font(.body)
Text("10g 25g 88g")
.foregroundColor(.gray)
.padding(.top, 80)
.padding(.bottom, 10)
.padding(.trailing, 10)
.frame(height:90)
if showRecipeOptions{
ReditorPopUp(shown: $showRecipeOptions)
.padding(.top, 20)
.padding(.leading, 15)
}
}
.padding(.top, -10)
.padding(5)
Button(action: {
}){
Image(systemName: "slider.horizontal.3")
.padding(.top, 10)
.foregroundColor(.black)
.onTapGesture{
showRecipeOptions.toggle()
}
}
.buttonStyle(BorderlessButtonStyle())
}
}
**BUTTON ATTACHED TO EACH ROW
struct ReditorPopUp: View {
#Binding var shown: Bool
#State var showEditRecipe = false
var body: some View {
if shown{
ZStack{
VStack (alignment: .leading, spacing: 20){
HStack(spacing:12){
Image(systemName: "pencil")
.font(.title2)
Button(action:{
showEditRecipe.toggle()
}){
Text("Edit")
.foregroundColor(.black)
} .buttonStyle(BorderlessButtonStyle())
//present editor
.fullScreenCover(isPresented: $showEditRecipe){
RecipeEditor()
}
}
HStack(spacing: 12){
Image(systemName: "trash")
.font(.title2)
.foregroundColor(.red)
Button(action: {
}){
Text("Delete")
.foregroundColor(.black)
} .buttonStyle(BorderlessButtonStyle())
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
///art of animation
.transition(.backslide)
.animation(.easeInOut(duration: 0.25))
}
}
}
struct RecipeFullListView: View {
#Environment(\.dismiss) var dismiss
#State var listofRecipes: [RecipeListModel] = RecipeList.recipes
#State private var active = false
init(){
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
let withIndex = listofRecipes.enumerated().map({ $0 })
ZStack{
VStack{
Text("Recipes")
//.padding(.bottom, -20)
//.padding(.top, 40)
.font(.title2)
List{
ForEach(withIndex, id: \.element.name){ index, recipe in
RecipeFullListRow(recipe: recipe, recipeName: recipe.name, index: index)
}
}
}//end of VStack
}
}
}
I have also attached a gif of the issue below
Instead of Bool make it index or row item type, so when button clicked instead of toggle assign current row/item, this will close previous automatically before show current. Of course you need to inject into the view index/item from ForEach level.
1)
if showRecipeOptions == index { // index of current row, ...
// if showRecipeOptions == item { // or item of current row
ReditorPopUp(shown: $showRecipeOptions)
.padding(.top, 20)
.padding(.leading, 15)
}
Button(action:{
showEditRecipe = index // index of tapped row
// showEditRecipe = index // or item of tapped row
}){
Update: simplified demo for idea (tested with Xcode 13.4 / iOS 15.5) - each row has own index (or unique item) and externally provided selection (either via binding or via view model in environment object).
#State private var active: Int? = nil // external single choice
var body: some View {
List {
ForEach(0..<10) {
Row(active: $active, index: $0) // << inject both
}
}
}
struct Row: View {
#Binding var active: Int?
let index: Int
var body: some View {
HStack {
Text("Item \(index)")
.onTapGesture { active = index } // I'm activated
Spacer()
// if I is activated show something...
if index == active {
Button("Done") { active = nil } // if needed to clean
}
}
.animation(.default, value: active)
}
}
Test module on GitHub
In your data model (ObservableObject) holding data for each row, you could add a variable holding the index of the row being selected for editing with #Published wrapper. Changing it would redraw the list with the edit view for new row.
I have a view with a toolbar on the bottom of the view. When clicked - two buttons are displayed. I am trying to achieve when the toolbar is pressed and the buttons are now displayed, the view (or background) becomes blurred/grayed out, except for the newly produced items.
I attached a screenshot of the desired effect I am aiming for.
struct UserDashController: View {
// #State private var showMealView = false
#State private var showSettingsView = false
#State private var showAddViews = false
#State private var angle: Double = 0
init(){
UIToolbar.appearance().barTintColor = UIColor.white
}
var body: some View {
NavigationView {
VStack{
Text("Blue me Please")
.frame(width: 400, height:600)
.background(.orange)
}
//sets setting bar top right
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
VStack{
Button(action: {
showSettingsView.toggle()
}) {
Image(systemName: "line.3.horizontal")
.font(.title3)
.foregroundColor(.black)
}
.sheet(isPresented: $showSettingsView){
JournalEntryMain()
}
}
}
// sets add meal option bottom/center
ToolbarItem(placement: .bottomBar) {
//displaying add meal and recipe icons when clicked
HStack{
Button(action: {
angle += 90
showAddViews.toggle()
}) {
if showAddViews {
VStack{
AddToolbar(showAddOptions: $showAddViews)
.offset(y:-50)
}
}
Image(systemName: "plus.square")
.opacity(showAddViews ? 0.5 : 1)
.font(.largeTitle)
.foregroundColor(.black)
.rotationEffect(.degrees(angle))
.animation(.easeIn(duration: 0.25), value: angle)
}
}
}
}
}
}
}
Buttons that appear when toolbar is pressed
struct AddToolbar: View {
#Binding var showAddOptions: Bool
#State var showMealView = false
var body: some View {
HStack{
VStack{
Button(action: {
showMealView.toggle()
}){
VStack{
Image(systemName: "square.and.pencil")
.font(.title)
.foregroundColor(.black)
.background(Circle()
.fill(.gray)
.frame(width:50, height:50))
.padding(3)
Text("Meal")
.foregroundColor(.black)
}
}.fullScreenCover(isPresented: $showMealView){
JournalEntryMain()
}
}
VStack{
Image(systemName: "text.book.closed")
.foregroundColor(.black)
.font(.title)
.background(Circle()
.fill(.gray)
.frame(width:50, height:50))
.padding(3)
Text("Recipe")
.foregroundColor(.black)
}
.offset(y: -50)
}
.frame(height:150)
}
}
Desired Effect
I'm a little confused by your desired effect example, partially because in the UI screenshot you attached, the background isn't blurred, it's just darkened. So, the following answer isn't tailored to your specific example but still should be able to help.
Let's say whatever variable you're using to determine whether or not to show the toolbar is showSettingsView. You could put the following modifiers on your background view:
To blur: .blur(showSettingsView ? 0.5 : 0.0)
To darken: .brightness(showSettingsView ? -0.5 : 0.0)
Obviously just replace "0.5" with whatever number feels best.
I have a form with several Boolean variables that change images on toggle, but when I click any of the buttons, all my variables change images? I have pulled all of them into separate subviews with the same result. Doesn't make any sense to me. Any help would be appreciated. Probably something simple I'm overlooking, but I'll be damned if I can see it.
Here is the code.
#State var total: String = ""
#State var date: Date = Date()
#State var bathroom: Bool = false
#State var steps: Bool = false
#State var furniture: Bool = false
#State var travel: Bool = false
#State var woodFloor: Bool = false
#State var concreteFloor: Bool = false
#State var takeUp: Bool = false
var body: some View {
VStack {
Form {
Section(header: Text("Job Info")
.font(.title2)
.foregroundColor(.blue)
.fontWeight(.bold)
.padding(.bottom, 10)
) {
VStack(alignment: .leading) {
DatePicker("Date", selection: $date)
.font(.title3)
.foregroundColor(.black)
.padding(.bottom, 10)
HStack(alignment: .center) {
Text("Total: $")
.font(.title3)
.foregroundColor(.black)
.padding(.trailing, 0)
TextField("", text: $total)
.font(.title3)
.frame(width: 100, height: 20, alignment: .leading)
.background(Color.yellow)
.foregroundColor(.black)
.cornerRadius(5)
Spacer()
}
.padding(.bottom, 20)
}
Section(header: Text("Addons")
.font(.title3)
.foregroundColor(.blue)
.padding(.bottom, 10)
) {
VStack(alignment: .leading, spacing: 5) {
Spacer()
HStack {
Text("Bathroom")
Spacer()
Button {
self.bathroom.toggle()
} label: {
Image(systemName: bathroom ? "checkmark.square" : "square")
}
}
HStack {
Text("Steps")
Spacer()
Button {
self.steps.toggle()
} label: {
Image(systemName: steps ? "checkmark.square" : "square")
}
}
HStack {
Text("Furniture")
Spacer()
Button {
self.furniture.toggle()
} label: {
Image(systemName: furniture ? "checkmark.square" : "square")
}
}
Spacer()
}
}
}
}
You put all buttons in one row (VStack with HStacks creates one view, so one row), and Form (being a List) sends all actions whenever any button is clicked in a row (it is designed to have one active element in a row).
So the solution would be either to remove VStack
Section(header: Text("Addons")
.font(.title3)
.foregroundColor(.blue)
.padding(.bottom, 10)
) {
VStack(alignment: .leading, spacing: 5) { // << this !!
Spacer()
and let every HStack with button live in own row...
... or instead of buttons use Image with tap gesture, like
HStack {
Text("Steps")
Spacer()
Image(systemName: steps ? "checkmark.square" : "square")
.padding()
.onTapGesture {
self.steps.toggle()
}
}
If you have a button inside a Section of Form the whole section will act as a button. So when you tap on it, all 3 button actions get executed.
instead, You can use Image with .onTapGesture{} modifier. In that way, you'll get what you want.
Sample code,
Image(systemName: bathroom ? "checkmark.square" : "square")
.foregroundColor(.blue)
.onTapGesture {
self.bathroom.toggle()
}
I think you are expecting the wrong thing from Form. Forms are not to make flexible lists with fancy stuff. If you want to be able to do anything you want and be able to customize your list at will, you should probably use Grids and other normal components like VStack...
How to use Form and what to expect:
The easiest way to see what you can do or not do with Forms without working around the limitations, is to go to the settings on your iPhone and see how different part of the settings are made. You can achieve almost all those stuff very easily without much work. However, if you want something even a little bit different than what you see in the settings, then you probably need a workaround to implement that in your app because Forms are not flexible.
Example of what you can do with Forms, taken from the settings:
For clarification, i am not saying you can't achieve what you want using forms. I'm saying thats most likely not a good idea to try to force forms to be something that they are not supposed to be.
The Answer:
So what should you do now? You should probably replace your button with Toggles so you get something similar to what you see in the settings.
[Other people have already said how to fix your current problem, so i'll just say the better way]
Consider using something like this:
struct ContentView: View {
#State var date = Date()
#State var total = ""
#State var bathroom = false
#State var steps = false
#State var furniture = false
var body: some View {
NavigationView { // DELETE this if the view before this view
// already has a navigation view
Form {
Section(header: Text("Job Info")) {
DatePicker("Date", selection: $date)
NavigationLink.init(
destination: FormTextFieldView(name: "Total $", value: $total),
label: {
Text("Total")
Spacer()
Text("$ " + total).foregroundColor(.secondary)
})
}
Section(header: Text("Addons")) {
Toggle("Bathroom", isOn: $bathroom)
Toggle("Steps", isOn: $steps)
Toggle("Furniture", isOn: $furniture)
}
}
.navigationBarTitle("Form", displayMode: .inline)
}
}
}
struct FormTextFieldView: View {
let name: String
#Binding var value: String
var body: some View {
Form {
TextField(name, text: $value)
}
.navigationBarTitle(name)
}
}
Why to use this?
First, because it is built with No Effort. You barely need to use any modifiers. So simple!
Second, this will work well on Any apple device. When you use modifiers, specially setting a frame for the view, you'll need to consider what will happen if you use e.g. an iPad. You don't need to worry about that when you are using this approach.
in my code I have this:
var body: some View {
NavigationView {
VStack(spacing: 0) {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 18) {
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 20){
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.blue)
.cornerRadius(15)
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.green)
.cornerRadius(15)
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.pink)
.cornerRadius(15)
}.padding(.horizontal, 12)
}
ForEach(specialtyList, id: \.type){ specialty in
NavigationLink (destination: SearchBar(item: specialty)){
VStack(spacing: 18) {
HStack{
Text(specialty.type).foregroundColor(.white)
specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}.frame(minWidth: 0, idealWidth: 350, maxWidth: .infinity)
.frame(height: 100)
}
}.padding(.horizontal)
.background(specialty.color).cornerRadius(45)
}.padding(.horizontal)
}
.padding(.top)
.padding(.bottom)
}
}.navigationBarTitle("Buscar")
}.accentColor(.black)
}
I want that, according to the button that is pressed in ForEach, the tittle of the next view is the name of the button. Now, the name displaying in every button is "Buscar"
I've tried to implement, after ForEach and before NavigationLink, a NavigationView, but the hole ForEach disappears. Basically, I want that the Text(specialty.type) that is pressed is the name of the navigation bar back button. Any ideas?
Here is a demo of possible approach - the idea is to change navigation bar title on the navigation link activation and reset on back.
Demo prepared & tested with Xcode 12 / iOS 14
struct DemoView: View {
#State private var barTitle = ""
#State private var selectedType = ""
#State private var isActive = false
let types = ["Type1", "Type2", "Type3"]
var body: some View {
NavigationView {
VStack {
ForEach(types, id: \.self) { type in
Button(type) {
self.isActive = true
self.selectedType = type
self.barTitle = type // comment for variant 2
}
}
}
.background(
NavigationLink(destination:
Text(selectedType)
// .onAppear { self.barTitle = self.selectedType } // variant 2
, isActive: $isActive) { EmptyView() }
)
.navigationBarTitle(barTitle)
.onAppear {
self.barTitle = "Buscar"
}
}
}
}
I created a modal but it seems to have a bug on the selection. When scrolling the left, it scrolls the right, I have to go to the very edge of the left to be able to scroll, this is how it looks:
import SwiftUI
struct ContentView: View {
#State var showingModal = false
#State var hours: Int = 0
#State var minutes: Int = 0
var body: some View {
ZStack {
VStack {
Button("Show me"){
self.showingModal = true
}
if $showingModal.wrappedValue {
VStack(alignment: .center) {
ZStack{
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// this one is it
VStack(spacing: 20) {
Text("Time between meals")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.yellow)
.foregroundColor(Color.white)
HStack {
Spacer()
VStack {
Picker("", selection: $hours){
ForEach(0..<4, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
VStack {
Picker("", selection: $minutes){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
}
Spacer()
Button(action: {
self.showingModal = false
}){
Text("Close")
} .padding()
}
.frame(width:300, height: 300)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
}
}
How can I fix that little bug? I tried playing around with the layout but no use... any help would be appreciated
What if I told you the reason your Picker not working was this line?
.cornerRadius(20).shadow(radius: 20)
Unfortunately, SwiftUI is still quite buggy and sometimes it doesn't do what it is supposed to do and especially Pickers are not that reliable. I guess we'll need to wait and see the next iteration of SwiftUI, but for now you can replace that line with the code below:
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(radius: 20)
There are just modifiers which affect all view hierarchy (ie. all subviews) that can change resulting layout/presentation/behaviour. And .cornerRadius and .shadow are such type modifiers.
The solution is to apply (as intended) those modifiers only to entire constructed view, and here it is
.compositingGroup() // <<< fix !!
.cornerRadius(20).shadow(radius: 20)
where .compositionGroup is intended to make above view hierarchy flat rendered and all below modifiers applied to only to that flat view.