I'm trying to build a custom list where the user can select an entry and the row will expand and show a picker. This picker should update an object (TimeItem) which stores the time information.
However, I was not able to use Binding in the ForEach Loop with Picker and I don't know why. The error message in Xcode is "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions".
I also tried to use ForEach(Array(items.enumerated()), id: \.1) instead of ForEach(items) to get the index of the current row but that would mess up the delete animation (but only sometimes!?).
I do not want to use the same Binding for each row (for ex. self.$selectedElement.minutes) - every row should have its own Binding.
Does anybody know how to fix this issue? Thanks for helping!
class TimeItem: Identifiable, Equatable, ObservableObject {
static func == (lhs: TimeItem, rhs: TimeItem) -> Bool {
lhs.id == rhs.id
}
let id = UUID()
#Published var minutes: Int = 0
#Published var seconds: Int = 30
}
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: elem == selectedElement ? 120 : 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("\(elem.minutes)")
.opacity(elem == selectedElement ? 0 : 1)
.transition(AnyTransition.scale)
if(elem == selectedElement){
HStack{
Picker(selection: elem.$minutes, label: Text("")){ // <- I can't use Binding with "elem"
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
Picker(selection: .constant(0), label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
}
.frame(height: 120)
.clipped()
}
HStack{
Button(action: {
self.items.removeAll { $0.id == elem.id }
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
self.items.append(TimeItem())
})
{
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}.animation(.spring(), value: items)
}
}
That case when you should do what compiler said: break up expression (ie. that big view) into distinct sub-expressions (ie. smaller subviews)
Here is fixed components (tested with Xcode 11.4 / iOS 13.4)
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in
ItemRowView(elem: elem, selectedElement: self.$selectedElement){
self.items.removeAll { $0.id == elem.id }
}
}
}
Spacer()
AddItemView {
self.items.append(TimeItem())
}
}.animation(.spring(), value: items)
}
}
struct SelectedElementView: View {
#ObservedObject var elem: TimeItem
var body: some View {
HStack{
Picker(selection: $elem.minutes, label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
Picker(selection: .constant(0), label: Text("")){
ForEach(0..<60){ i in
Text("\(i)")
}
}
.frame(width: 120)
.clipped()
}
.frame(height: 120)
.clipped()
}
}
struct AddItemView: View {
let action: ()->()
var body: some View {
Button(action: action)
{
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}
}
struct ItemRowView: View {
#ObservedObject var elem: TimeItem
#Binding var selectedElement: TimeItem?
let action: ()->()
var body: some View {
ZStack{
Rectangle()
.cornerRadius(12)
.frame(height: elem == selectedElement ? 120 : 40)
.foregroundColor(Color.gray.opacity(0.15))
Text("\(elem.minutes)")
.opacity(elem == selectedElement ? 0 : 1)
.transition(AnyTransition.scale)
if(elem == selectedElement){
SelectedElementView(elem: elem)
}
HStack{
Button(action: action)
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = self.elem
}
}
}
}
Related
I have a content in the safe area that reveals details on a button tap. I would like to allow the user to drag the handle to expand the content details as well, almost like the bottom sheet behaviour.
This is what I have so far:
struct ContentView: View {
#State private var isExpanded = false
var body: some View {
ScrollView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.frame(maxWidth: .infinity)
.padding()
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 24) {
Capsule()
.frame(width: 50, height: 5)
.opacity(0.5)
HStack(spacing: 16) {
Image(systemName: "list.bullet")
Spacer()
VStack {
Text("Track 2")
}
.font(.caption)
Spacer()
Image(systemName: "backward.fill")
Image(systemName: "play.fill")
Image(systemName: "forward.fill")
Spacer()
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
if isExpanded {
HStack {
Image(systemName: "airplayaudio")
Image(systemName: "timer")
Spacer()
Text("Artist")
}
}
}
.padding([.bottom, .horizontal])
.padding(.top, 8)
.background(.ultraThinMaterial)
}
}
}
How can I add functionality to the Capsule to drag the handle to expand that animates in a way to stretch the content in an elastic way (not just swipe to expand, but to drag the content until the user let's go)?
You can use a DragGesture on your Capsule:
.gesture(
DragGesture()
.onEnded { value in
withAnimation(.spring()) {
let swipingUp = value.translation.height < 0
withAnimation {
isExpanded = swipingUp ? true: false
}
}
}
)
To allow for a stretch, you can use onChanged paired with a State variable:
struct ContentView: View {
#State private var isExpanded = false
#State private var stretch = CGFloat.zero
var body: some View {
ScrollView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.frame(maxWidth: .infinity)
.padding()
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 24) {
Capsule()
.frame(width: 50, height: 5)
.opacity(0.5)
.gesture(
DragGesture()
.onChanged { newValue in
withAnimation(.spring()) {
stretch = abs(newValue.translation.height)
}
}.onEnded { value in
withAnimation(.spring()) {
isExpanded = value.translation.height < 0
stretch = .zero
}
}
)
HStack(spacing: 16) {
Image(systemName: "list.bullet")
Spacer()
VStack {
Text("Track 2")
}
.font(.caption)
Spacer()
Image(systemName: "backward.fill")
Image(systemName: "play.fill")
Image(systemName: "forward.fill")
Spacer()
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
if isExpanded {
HStack {
Image(systemName: "airplayaudio")
Image(systemName: "timer")
Spacer()
Text("Artist")
}
}
if stretch > 0 && !isExpanded {
Color.clear
.frame(height: stretch)
}
}.padding([.bottom, .horizontal])
.padding(.top, 8)
.background(.ultraThinMaterial)
}
}
}
I'm making a swiftui app, I have a NavigationView that contains a VStack and a List inside.
I've tried to put the following line of code in a lot of places .background(Color.blue) but it had no effect anywhere (nothing happened).
How can I set a background for the view itself?
I know it’s a very simple thing, but it doesn’t work out at all...
This is my code:
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Todo.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Todo.name, ascending: true)]) var todos: FetchedResults<Todo>
#State private var showingAddTodoView: Bool = false
#State private var animatingButton: Bool = false
var body: some View {
NavigationView {
ZStack {
List {
ForEach(self.todos, id: \.self) { todo in
HStack {
Circle()
.frame(width: 12, height: 12, alignment: .center)
.foregroundColor(self.colorize(priority: todo.priority ?? "Normal"))
Text(todo.name ?? "Unknown")
.fontWeight(.semibold)
Spacer()
Text(todo.priority ?? "Unkown")
.font(.footnote)
.foregroundColor(Color(UIColor.systemGray2))
.padding(3)
.frame(minWidth: 62)
.overlay(
Capsule().stroke(Color(self.colorize(priority: todo.priority ?? "Normal")), lineWidth: 0.75)
)
Spacer()
Image(systemName: todo.completed ? "checkmark.square": "square")
.foregroundColor(todo.completed ? .green : .black)
.onTapGesture {
self.updateTodo(todo)
}
}
.padding(.vertical, 10)
}
.onDelete(perform: deleteTodo)
}
.navigationBarTitle("Todos", displayMode: .inline)
.navigationBarItems(
leading: EditButton(),
trailing:
Button(action: {
self.showingAddTodoView.toggle()
}) {
Image(systemName: "plus")
}
.sheet(isPresented: $showingAddTodoView) {
AddTodoView().environment(\.managedObjectContext, self.managedObjectContext)
}
)
if todos.count == 0 {
NoTodosView()
}
}
.sheet(isPresented: $showingAddTodoView) {
AddTodoView().environment(\.managedObjectContext, self.managedObjectContext)
}
.overlay(
ZStack {
Group {
Circle()
.fill(Color.blue)
.opacity(self.animatingButton ? 0.2 : 0)
.scaleEffect(self.animatingButton ? 1 : 0)
.frame(width: 68, height: 68, alignment: .center)
Circle()
.fill(Color.blue)
.opacity(self.animatingButton ? 0.15 : 0)
.scaleEffect(self.animatingButton ? 1 : 0)
.frame(width: 88, height: 88, alignment: .center)
}
.animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true))
Button(action: {
self.showingAddTodoView.toggle()
}) {
Image(systemName: "plus.circle.fill")
.resizable()
.scaledToFit()
.background(Circle().fill(Color("Color")))
.frame(width: 48, height: 48, alignment: .center)
}
.onAppear(perform: {
self.animatingButton.toggle()
})
}
.padding(.bottom, 15)
.padding(.trailing, 15)
, alignment: .bottomTrailing
)
}
}
I want to set the background color for this view:
try this...
struct ContentView: View {
var body: some View {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
return NavigationView {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
List{
ForEach((1...5), id: \.self){_ in
HStack{
Text("item")
}
}
// .listRowBackground(Color.blue)
}
}
}
}
}
from last post I decided on creating a table as following. However, when I type inside one textfield, all other textfields in the same ForEach pass through the same input. Is there something I can do, so each textfield will have its own entity?
Additionally, is this still a good solution, for when I have a few of these inside 1 view? And is this too much in terms of too many Spacers and VStacks?
How can I track all of these inputs from the textfields individually to store in coreData?
Thanks, I appreciate any type of advice.
Kind Regards
import SwiftUI
struct SwiftUIView: View {
#State var B: String = ""
#State var C: String = ""
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 22)
.fill(Color(.systemGray5))
.frame(width: 400, height: 130, alignment: .center)
VStack (alignment: .leading){
HStack {
Text("A")
.fontWeight(.bold)
Spacer()
ForEach(1..<6) { i in
Text("\(i)")
.fontWeight(.bold)
}
.frame(width: 50)
}
HStack {
Text("B")
Spacer()
ForEach(1..<6) { i in
TextField("", text: $B)
.background(
VStack {
Spacer()
Color
.primary
.frame(height: 1)
}
)
.frame(width: 50, alignment: .trailing)
}
}
HStack {
Text("C")
Spacer()
ForEach(1..<6) { i in
TextField("", text: $C)
.background(
VStack {
Spacer()
Color
.primary
.frame(height: 1)
}
)
.frame(width: 50, alignment: .trailing)
}
}
}
.frame(width: 350, height: 100, alignment: .center)
}
}
}
You are using the same variables for your TextFields within each ForEach. You need to use an array of something, and key your ForEach to the array. Here is a simplified version of your code. Notice how "B" duplicates your typing in each TextField where as "C" doesn't? The difference is that each TextField in "C" is accessing its own storage.
struct SwiftUIView: View {
#State var B: String = ""
#State var C: [DemoText] = [DemoText(value: ""), DemoText(value: "")]
var body: some View {
VStack (alignment: .leading){
HStack {
Text("B")
Spacer()
ForEach(0..<2) { _ in
TextField("", text: $B)
.background(Color.gray)
}
}
HStack {
Text("C")
Spacer()
ForEach($C) { $element in
TextField("", text: $element.value)
.background(Color.gray)
}
}
}
}
}
struct DemoText: Identifiable {
let id = UUID()
var value: String
}
I'm having an issue similar to the following post .onReceive firing twice.
I have a picker that fires .onChange twice. I am using a model data Environment object for the picker.
Is there a way for me to get the before state such that I can compare if the new_haveCount value is truely changing? Or better yet, to prevent the double fire in the first place?
#EnvironmentObject var modelData: ModelData
specifics and specificsFirebase are both structures.
Picker code
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) { _ in
saveSpecifics()
}
From the apple dev page, .onChange seems to have a before and ofter property.
struct PlayerView : View {
var episode: Episode
#State private var playState: PlayState = .paused
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(playState: $playState)
}
.onChange(of: playState) { [playState] newState in
model.playStateDidChange(from: playState, to: newState)
}
}
}
Full View if it helps
import SwiftUI
import Firebase
struct SpecificsEntryView: View {
#EnvironmentObject var modelData: ModelData
let figure: Figure
var figureIndex: Int {
modelData.figureArray.firstIndex(where: { $0.id == figure.id })!
}
var body: some View {
HStack(spacing: 4) {
// new labels
VStack(alignment: .leading, spacing: 4) {
ForEach(kSpecificType_Labels, id: \.self) { label in
Text(label)
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
Divider()
}
}
// new values
VStack(alignment: .center, spacing: 4) {
Text(kNewText)
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[2]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[3]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[4]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
} // end new vstack
Divider() // vertical
// loose values
VStack(alignment: .center, spacing: 4) {
Text(kLooseText)
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[2]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) { newVal in
print("\(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) to \(newVal)")
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount,
label: Text(" \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount) ")) {
ForEach(0 ..< 20) {
Text("\(kSpecificType_Labels[3]) \($0) \(kNewText.lowercased())")
}
}
.onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount) { _ in
saveSpecifics()
}
.frame(maxHeight: .infinity)
.padding(.bottom, 2)
.pickerStyle(MenuPickerStyle())
Divider()
TextField("Order from", text: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderText,
onCommit: {
saveSpecifics()
})
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.bottom, 2)
.background(Color(.systemGray5))
.cornerRadius(4)
Divider()
} // end loose vstack
} // end all hstack specifics
.fixedSize(horizontal: false, vertical: true)
.font(/*#START_MENU_TOKEN#*/.title/*#END_MENU_TOKEN#*/)
} // end body
// save specifics on update
func saveSpecifics() {
// Inject Firebase authentication
let userID = Auth.auth().currentUser?.uid
modelData.figureArray[figureIndex].specifics.specificsFirebase.saveSpecifics(userID: userID!)
}
}
I ran into the same capture list syntax issue and fixed it by using an explicit alias for the captured value from the EnvironmentObject variable (Xcode gave a hint). Like so:
.onChange(model.someVariable) {[oldValue = model.someVariable] newValue in { ... }
But my code still causes .onChange to fire twice...
I was having a similar issue, but I was getting 4 duplicate .onChange handler calls on a DatePicker control.
I ended up changing my code to use the Binding extension from here and the duplicate calls went away https://www.hackingwithswift.com/quick-start/swiftui/how-to-run-some-code-when-state-changes-using-onchange
I'd still like to know what was causing the duplicate calls if someone has any insight.
I am creating a custom list displaying time information (minutes and seconds, not used in the snippet below to simplify the code). I managed to implement a nice animation when the user adds an entry to the list, but deleting an entry has no animation (1st GIF).
With iOS 14, the animation is working, however, the animation only removes the last rectangle from the list and then updates the text in each row (2nd GIF). That's not what I want - My goal is if a row has been deleted, the other rows should fill up that space and move accordingly - with an animation.
Probably something is wrong with the IDs of the rows but I just wasn't able to fix that. Thanks for helping!
struct ContentView: View {
#State var minutes = [0]
#State var seconds = [0]
#State var selectedElement = 0
var body: some View {
ScrollView(){
VStack{
ForEach(minutes.indices, id: \.self){ elem in
ZStack{
EntryBackground()
Text("\(self.minutes[elem])")
.transition(AnyTransition.scale)
HStack{
Button(action: {
withAnimation(.spring()){
self.seconds.remove(at: elem)
self.minutes.remove(at: elem)
}
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
withAnimation{
self.minutes.append(self.minutes.count)
self.seconds.append(0)
}
})
{
ZStack{
EntryBackground()
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}
}
}
struct EntryBackground: View {
var body: some View {
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
}
}
You need to make each row uniquely identified, so animator know what is added and what is removed, so animate each change properly.
Here is possible approach. Tested with Xcode 12 / iOS 14
struct TimeItem: Identifiable, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
let id = UUID() // << identify item
let minutes: Int
let seconds: Int = 0
}
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in // << work by item
ZStack{
EntryBackground()
Text("\(elem.minutes)")
.transition(AnyTransition.scale)
HStack{
Button(action: {
self.items.removeAll { $0.id == elem.id }
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
self.items.append(TimeItem(minutes: self.items.count))
})
{
ZStack{
EntryBackground()
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}.animation(.spring(), value: items) // << animate changes
}
}
struct EntryBackground: View {
var body: some View {
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
}
}