Long List with Textfields in SwiftUI - ios

I try to do a List with SwiftUI where you can insert two Strings in Textfields and one Boolean Button in each Row. However when the list exceeds the screen and you scroll to the last rows they sometimes erase the content.
I created a minimal example:
struct ContentView: View {
#State var bindOne = "one"
#State var bindTwo = "two"
#State var bindThree = "three"
#State var bindFour = "four"
#State var bindFive = "five"
#State var bindSix = "six"
#State var buttonValue = false
var body: some View {
VStack{
Text("test")
.font(.largeTitle)
List{
Row(someBind: bindOne, buttonValue: false)
Row(someBind: bindTwo, buttonValue: false)
Row(someBind: bindThree, buttonValue: false)
Row(someBind: bindFour, buttonValue: false)
Row(someBind: bindFive, buttonValue: false)
Row(someBind: bindSix, buttonValue: false)
}
}
}
}
with the supporting View:
struct Row: View {
#State var someBind: String
#State var buttonValue: Bool
var body: some View {
HStack{
TextField(someBind, text: $someBind)
.font(.largeTitle)
TextField(someBind, text: $someBind)
.font(.largeTitle)
Button(action: {self.buttonValue.toggle()}){
if buttonValue{
Text("Yes")
.font(.largeTitle)
}else{
Text("No")
.font(.largeTitle)
}
}
}
.padding(.vertical, 70)
}
}
The result is
When the lines fit on the screen there is no problem, but sometimes you just have a long list.
Is this a bug or am I missing something?

The problem is not present anymore. Checked with Xcode Version 11.4.1 (11E503a).

Related

SwiftUI Tabbed View prevent swipe to next tab [duplicate]

This question already has answers here:
SwiftUI TabView PageTabViewStyle prevent changing tab?
(2 answers)
Closed 7 months ago.
This post was edited and submitted for review 7 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I'm trying to prevent swiping to the second tab until the user has clicked a button on the first tabbed view indicating the data is complete. Then the user can swipe. I thought it was #State in the first tab view since it's the source of truth and a #Binding in the main content view, but that didn't work. I've simplified the code and removed the Binding info so that at least it will build. Any thoughts or better approach appreciated. (Quite new at this...)
//ContentView.swift file
struct ContentView: View {
#State private var selection = 0
#State var allowSwipeTo2 = false
var body: some View {
VStack {
Text("Main Content View")
Text(String(allowSwipeTo2)) //added this line, and can see it change from false to true when button is clicked, but still not swipable
.font(.system(size: 18))
}
TabView(selection: $selection){
tab1View(allowSwipeTo2: $allowSwipeTo2) //modified based on recommendation
.tag(0)
if allowSwipeTo2 == true {
tab2View()
.tag(1)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
//tab1View.swift file with the button and the booleans for disabling further changes and ok to swipe:
struct tab1View: View {
#State private var p8_10 = 0
#State private var stepperDisabled = false
#Binding var allowSwipeTo2: Bool //modified based on recommendation
#State private var showingAlert = false
var body: some View {
VStack{
HStack{
Text("Things")
Stepper("Total: \(p8_10)", value: $p8_10, in: 0...15)
}.font(.system(size: 15))
.disabled(stepperDisabled)
HStack{
Spacer()
Button("Entry Complete") {
showingAlert = true
}.alert(isPresented: $showingAlert){
Alert(
title: Text("Entry Complete?"),
message: Text("You will no longer be able to change your entry"),
primaryButton: .cancel(),
secondaryButton: .destructive(
Text("OK"),
action:{
stepperDisabled = true
allowSwipeTo2 = true
})
)
}
Spacer()
}
}
}
}
//tab2View.swift file
struct tab2View: View {
var body: some View {
Text("Tab 2 Content!")
.font(.system(size: 15))
}
}```
Small wrong approach. In your ContentView, you are supposed to bind the allowSwipeTo2 variable with the tab1View.
To fix your problem, change the type of variable allowSwipeTo2 in tab1View to #Binding var allowSwipeTo2 : Bool, and finally in your ContentView, call the first tab view like this tab1View(allowSwipeTo2: $allowSwipeTo2)
I have modified the code as below. You can find the modified parts with this comment //modified.
struct ContentView: View {
#State private var selection = 0
#State var allowSwipeTo2 = false
var body: some View {
VStack {
Text("Main Content View")
.font(.system(size: 18))
}
TabView(selection: $selection){
tab1View(allowSwipeTo2: $allowSwipeTo2) //modified
.tag(0)
if allowSwipeTo2 == true {
tab2View()
.tag(1)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
struct tab1View: View {
#State private var p8_10 = 0
#State private var stepperDisabled = false
#Binding var allowSwipeTo2 : Bool //modified
#State private var showingAlert = false
var body: some View {
VStack{
HStack{
Text("Things")
Stepper("Total: \(p8_10)", value: $p8_10, in: 0...15)
}.font(.system(size: 15))
.disabled(stepperDisabled)
HStack{
Spacer()
Button("Entry Complete") {
showingAlert = true
}.alert(isPresented: $showingAlert){
Alert(
title: Text("Entry Complete?"),
message: Text("You will no longer be able to change your entry"),
primaryButton: .cancel(),
secondaryButton: .destructive(
Text("OK"),
action:{
stepperDisabled = true
allowSwipeTo2 = true
})
)
}
Spacer()
}
}
}
}
struct tab2View: View {
var body: some View {
Text("Tab 2 Content!")
.font(.system(size: 15))
}
}

SwiftUI Picker Does Not Scroll to Selection

I'm trying to use the SwiftUI 2 ScrollViewReader with a picker so that tapping on the
picker displays the picker list with the current selection in the view (preferably to an anchor). Nothing that I have tried, including the below, even has any effect on the list. It seems like this should be simple.
struct ContentView: View {
#State private var topIntYears: [Int] = createIntYearList()
#State private var startYear = 1923
#State private var startDollars: String = "1"
var body: some View {
VStack {
Text("Do Something")
NavigationView {
ScrollViewReader { svr in
Form {
Picker("Base Year", selection: $startYear) {
ForEach(topIntYears, id:\.self) {
Text(String($0))
//this does not work
.simultaneousGesture(
TapGesture()
.onEnded { _ in
print("Picker tapped")
svr.scrollTo(startYear)
}
)
}
}//pick
TextField("Base dollars", text: $startDollars)
.keyboardType(.decimalPad)
.padding()
}//form
}//svr
}//nav
}//v
}//body
}
func createIntYearList() -> [Int] {
var years: [Int] = []
for y in 1898...2021 {
years.append(y)
}
return years
}//int years
Xcode 12.4, iOS 14.4 Any guidance would be appreciated.

SwiftUI NavigationItem doesn't respond

When I click on the left arrow it should dismiss the view, but only the UI responds as the button being clicked and the view pushed by a NavigationLink is not dismissed...
The same view pushed by another NavigationLink in another view works perfectly, but when pushed by this NavigationLink, I click on the left arrow, only 1 in 20 times it dismisses the view. Is it bug in SwiftUI again? Is there a workaround?
import SwiftUI
import Firebase
import struct Kingfisher.KFImage
struct SearchView: View {
#Environment(\.presentationMode) var presentation
#State var typedSearchValue = ""
#State var createNewPost = false
#State var showMessaging = false
#EnvironmentObject var userInfo : UserData
#Binding var switchTab: Int
#State var text : String = ""
#State var foundUsers: [FetchedUser] = []
#State var showAccount = false
#State var fetchedUser = FetchedUser()
var body: some View {
NavigationView {
ZStack{
// navigate to that user's profile << the problem navlink
NavigationLink(destination:
UserAccountView(fetchedUser: self.$fetchedUser, showAccount: self.$showAccount)
.environmentObject(self.userInfo)
.environmentObject(FetchFFFObservable())
,isActive: self.$showAccount
){
EmptyView()
}.isDetailLink(false)
//...
NavigationLink(destination:
MessagingMainView(showMessaging: self.$showMessaging)
.environmentObject(UserData())
.environmentObject(MainObservable()),
isActive: self.$showMessaging
){
Text("")
}
VStack(spacing: 0) {
SearchBarMsg(text: $text, foundUsers: $foundUsers)
.environmentObject(userInfo)
.padding(.horizontal)
VStack{
if text != ""{
List(foundUsers, id: \.username){ user in
FetchedUserCellView(user: user)
.environmentObject(self.userInfo)
.onTapGesture {
self.fetchedUser = user
self.showAccount = true
}
}
}else{
//....
}
}
}
.navigationBarColor(.titleBarColor)
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
Navigates to this view, and the button in navigationItems doesn't work, although the UI responds:
struct UserAccountView: View {
#Environment(\.presentationMode) var presentation
//...
#Binding var showAccount : Bool
var body: some View {
VStack {
//.....
}
.navigationBarColor(.titleBarColor)
.navigationBarTitle(Text(fetchedUser.username), displayMode: .inline)
.navigationBarItems(leading: Button(action: {
//this button doesn't work!
self.showAccount = false
}, label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 15)
})).accentColor(.white)
)
}
}

Dismissing custom modal / overlay not updating contents of View In SwiftUI

Update: I refactored it a bit to use #ObservableObject, but the issue still persists
In my SwiftUI app, I have a LazyVGrid populated with n cards and one New Event button. When I press the New Event button, I have a custom modal overlaying the View to let the user add a new event. However, after dismissing the view (by tapping a Create Event button), the modal disappears however the new event card isn't added to the LazyVGrid. It only appears once I force quit the app and re-open it.
Here is the main ContentView:
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model
var columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
func animate(while condition: Bool) {
self.angle = condition ? -0.6 : 0
withAnimation(Animation.linear(duration: 0.125).repeatForever(while: condition)) {
self.angle = 0.6
}
if !condition {
self.angle = 0
}
}
var body: some View {
ZStack {
NavigationView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(model.events, id: \.self) { event in
SmallCardView(event: event, angle: $angle)
}
if !showModal {
AddEventButtonView(
editMode: $editMode,
showModal: $showModal,
namespace: namespace
)
} else {
Spacer().frame(height: 100)
}
}
.onLongPressGesture {
if !self.editMode {
self.editMode = true
animate(while: self.editMode)
}
}
}
if self.showModal {
AddEventView(namespace: namespace, events: self.$model.events, showModal: $showModal)
}
}
}
}
This is the View for the Add Event button:
struct AddEventButtonView: View {
#Binding var editMode: Bool
#Binding var showModal: Bool
var namespace: Namespace.ID
var body: some View {
Image(systemName: "calendar.badge.plus")
.frame(height: 100)
.frame(maxWidth: .infinity)
.opacity(self.editMode ? 0.0 : 1.0)
.onTapGesture {
withAnimation {
self.showModal = true
}
}
}
}
and this is my modal view:
struct AddEventView: View {
#State var eventName: String = ""
#State var endDate = Date()
#State var gradientIndex: Int = 0
#Binding var showModal: Bool
#Binding var events: [Event]
var namespace: Namespace.ID
init(namespace: Namespace.ID, events: Binding<[Event]>, showModal: Binding<Bool>) {
self.namespace = namespace
self._events = events
self._showModal = showModal
}
var body: some View {
VStack(spacing: 30.0) {
//...
Button(action: {
let event = Event(name: eventName, start: .init(), end: endDate, gradientIndex: gradientIndex)
self.events.append(event)
withAnimation {
self.showModal = false
}
}) {
RoundedRectangle(cornerRadius: 13)
.frame(maxWidth: .infinity)
.frame(height: 42)
.overlay(
Text("Add Event")
.foregroundColor(.white)
.bold()
)
}
.disabled(self.eventName.isEmpty)
}
}
}
If anyone has any idea of how to update the LazyVGrid to show the new event card when a new event is added that'd be awesome, thanks!
Here's the problem:
It is not clear how you did set up model, but it should be observed to update view, as (scratchy)
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model // << here !!
...
or use #EnvironmentObject pattern with injection via .environmentObject
or for Swift 2.0 use
#StateObject var model: Model = Model.shared

#Binding properties are not refresh view of Child View in SwiftUI

I'm trying to reusable View and I added it on ContentView
This is my Child View
struct VStackView: View {
#Binding var spacing: Double
#Binding var alignmentIndex: Int
#Binding var elementsCount: Int
private let alignments: [HorizontalAlignment] = [.leading, .center, .trailing]
var body: some View {
VStack(alignment: self.alignments[alignmentIndex], spacing: CGFloat(spacing)) {
ForEach(0..<elementsCount) {
Text("\($0)th View")
}
}
}
}
and This is SuperView
Superview has Controls like Stepper, Slider, Picker that adjust values of VStack (alignment, spacing etc)
and I want to show the result depending on that values. but Child View is not changed
struct LayoutView: View {
private let layout: StackLayout
#State private var spacing = 0.0
#State private var alignmentIndex = 0
#State private var alignment: HorizontalAlignment = .leading
#State private var elementsCount: Int = 0
private let alignmentsString = [".leading", ".center", ".trailing"]
private let minValue = 0.0
private let maxValue = 100.0
init(_ layout: StackLayout) {
self.layout = layout
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Controls")) {
VStack(alignment: .leading) {
Text("Spacing: \(Int(spacing))").font(.caption)
HStack {
Text("\(Int(minValue))")
Slider(value: $spacing, in: minValue...maxValue, step: 1)
Text("\(Int(maxValue))")
}
Divider()
Picker("alignment", selection: $alignmentIndex) {
ForEach(0..<self.alignmentsString.count) {
Text("\(self.alignmentsString[$0])")
}
}.pickerStyle(SegmentedPickerStyle())
Divider()
Stepper(value: $elementsCount, in: 0...10) {
Text("Element Count: \(elementsCount)")
}
}
}
VStackView(spacing: $spacing, alignmentIndex: $alignmentIndex, elementsCount: $elementsCount)
}
.navigationBarTitle(Text(layout.rawValue), displayMode: .inline)
}
}
}
I also search google and they recommend #EnviornmentObject. if that is correct, when to use #Binding property wrapper.
Isn't it two way binding properties?
Simply speaking you can use #Binding, when you want to share data in two places.
#Observable or #environmetobject is to be used, when you want to share your data in multiple views.
Your ForEach Loop in the VStackView generates a problem, because Swiftui does not know how it can identify each of your items uniquely so it does not know how to update them, when values change.
Append your code like this:
ForEach(0..<elementsCount, id: \.self) {
Text("\($0)th View")
}

Resources