Al TextFields in Foreach type simultaneously in SwiftUI - foreach

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
}

Related

How can i animate data fetched from API in my ScrollView/LazyVGrid?

This is my main view where i'm using AsyncAwait to fetch the data. I wanna have the effect of nice animation when i receive the data. Tried a lot of things, but none worked. Some animations were successful but only after opening the specific view for the 3-rd + time. On first opening i could never achieve that effect. Would really appreciate if someone could tell me what should i do. Thanks in advance :)
struct PresentationRootView: View {
// setting up the default value for segmented control
#State private var selectedSegmentedOption: SegmentedSelection = .myPresentations
#ObservedObject var viewModel: PresentationsViewModel
var body: some View {
let columns = [GridItem(.flexible())]
VStack(spacing: 0) {
Picker(selection: $selectedSegmentedOption) {
Text("My Presentations").tag(SegmentedSelection.myPresentations)
Text("Templates").tag(SegmentedSelection.templates)
} label: {}
.pickerStyle(.segmented)
}
ScrollView {
if selectedSegmentedOption == .myPresentations {
if viewModel.shouldShowEmptyScreen {
Text("No data")
}
LazyVGrid(columns: columns, spacing: 4, content: {
ForEach($viewModel.myPresentations, id: \.ID) { element in
PresentationSingleView(url: element.ThumbURLWithSas.wrappedValue, title: element.Name.wrappedValue, numberOfResources: 4)
.padding([.leading, .trailing], 13)
}
}).task {
await viewModel.getAllPresentations(page: 1, pageSize: 10)
}
}
}
.background(CustomColor.background.swiftUIColor)
Spacer()
.navigationTitle("Presentations")
}
}
struct PresentationSingleView: View {
// replace this with the real data
var url: URL
var title: String
var numberOfResources: Int
var isIphone = DefaultAppManager.shared.isIphone
#StateObject var image = FetchImage()
var body: some View {
HStack(alignment: .top) {
image.view?
.resizable()
.scaledToFill()
.frame(maxWidth: isIphone ? 90 : 140, maxHeight: isIphone ? 64 : 140)
.cornerRadius(4)
.padding(isIphone ? 10 : 20)
VStack(alignment: .leading) {
Text(title)
.font(Font.custom("Roboto-Regular", size: 12))
.padding(.top, 20)
Spacer()
HStack(alignment: .center) {
Image("presentationResourceIcon")
Text("\(numberOfResources) resources")
.font(Font.custom("Roboto-Medium", size: 12))
Spacer()
Image("more_options")
.onTapGesture {
print("tapped on right image!!!")
}
}
.padding(.bottom, 20)
}
Spacer()
}
.onAppear {
image.priority = .normal
image.load(url)
}
.frame(height: isIphone ? 90 : 150)
.background(Color.white)
.cornerRadius(8)
}
}

How to hide TabView in NavigationLink?

First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.

Passing a value to a text box in navigation link view

I am trying to pass a value from a text box in my first view to a text box in my second view.
struct FirstView: View {
#State private var inputTextValue = ""
var body: some View {
NavigationView{
VStack {
TextField("",text: $inputTextValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
NavigationLink(destination: {SecondView(incomingTextFieldvalue: inputTextValue)}, label: {Text("To second view")})
}
}
}
struct SecondView: View {
var incomingTextFieldvalue: String
#State var textFieldValue = "Initial Value in view 2"
var body: some View {
VStack {
Spacer()
TextField("",text: $textFieldValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
Spacer()
Text("Incoming value: \(incomingTextFieldvalue)")
Text("This value: \(textFieldValue)")
Spacer()
}
}
So, what I want exactly is that the text box in the second view has the passed value filled in initially.
I have tried various solutions, but have not been able to get the solution that I need. Please let me know if I need to provide more info.
We can set incoming value to internal in onAppear, like
VStack {
Spacer()
TextField("",text: $textFieldValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
Spacer()
Text("Incoming value: \(incomingTextFieldvalue)")
Text("This value: \(textFieldValue)")
Spacer()
}
.onAppear { textFieldValue = incomingTextFieldvalue } // << here !!
Tested with Xcode 13.4 / iOS 15.5
*(of course if you don't want to share them, which is unclear)
You need to use #Binding like so:
In FirstView:
struct FirstView: View {
#State private var inputTextValue = ""
var body: some View {
NavigationView{
VStack {
TextField("",text: $inputTextValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
NavigationLink(destination: SecondView(incomingTextFieldvalue: $inputTextValue), label: {Text("To second view")})
}
}
}
}
In SecondView:
struct SecondView: View {
#State var textFieldValue = "Initial Value in view 2"
#Binding var incomingTextFieldvalue: String
var body: some View {
VStack {
Spacer()
TextField("",text: $incomingTextFieldvalue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
Spacer()
Text("Incoming value: \(incomingTextFieldvalue)")
Text("This value: \(textFieldValue)")
Spacer()
}
}
}
Also note that when passing a destination for the NavigationLink you don't need to use {}
You don’t need another variable to do that; just modify the #State variable itself.
struct FirstView: View {
#State private var inputTextValue = ""
var body: some View {
NavigationView{
VStack {
TextField("",text: $inputTextValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
NavigationLink(destination: {SecondView(textFieldValue: inputTextValue)}, label: {Text("To second view")})
}
}
}
}
struct SecondView: View {
#State var textFieldValue = "Initial Value in view 2"
var body: some View {
VStack {
Spacer()
TextField("",text: $textFieldValue)
.frame(width: 200, height: 30, alignment: .center)
.border(.gray)
Spacer()
Text("This value: \(textFieldValue)")
Spacer()
}
}
}

How to add more padding bellow a TextView when the keyboard is shown

When i have TextField inside ScrollView and tap on it the keyboard is shown as expected. But it seems that the TextField is moved up just enough to show the input area but i want to be moved enough so that is visible in its whole. Otherwise it looks cropped. I couldn't find a way to change this behaviour.
struct ContentView: View {
#State var text:String = ""
var body: some View {
ScrollView {
VStack(spacing: 10) {
ForEach(1...12, id: \.self) {
Text("\($0)…")
.frame(height:50)
}
TextField("Label..", text: self.$text)
.padding(10)
.background(.white)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.blue, lineWidth: 1)
)
}
.padding()
.background(.red)
}
}
}
Use a .safeAreaInset modifier.
#State var text:String = ""
var body: some View {
ScrollView {
VStack(spacing: 10) {
ForEach(1...12, id: \.self) {
Text("\($0)…")
.frame(height:50)
}
TextField("Label..", text: self.$text)
.padding(10)
.background(.white)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.blue, lineWidth: 1)
)
}
.padding()
.background(.red)
}.safeAreaInset(edge: .bottom) { //this will push the view when the keyboad is shown
Color.clear.frame(height: 30)
}
}
You can provide additional padding to the view (and it works even in iOS 13 and 14):
struct ContentView: View {
#State var text:String = ""
var body: some View {
ScrollView {
VStack(spacing: 10) {
ForEach(1...12, id: \.self) {
Text("\($0)…")
.frame(height:50)
}
TextField("Label..", text: self.$text)
.padding(10)
.background(.white)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.blue, lineWidth: 1)
)
.padding(.bottom, 32) //here, set as much pasding as you want
}
.padding()
.background(.red)
}
}
}

SwiftUI custom List cannot use Binding in ForEach

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

Resources