How do I fix buttons not being tappable in a ForEach loop? - swift

I have a view which contains a button. The view with the button is created from a forEach loop. For some reason only some buttons are tappable and others are not.
The parent view contains a NavigationView, a scroll view inside the NavigationView, a lazyVStack inside of the scroll view, a forEachloop in that lazyVStack and in that for loop is the child view that contains the button.
struct ContentView: View {
let peoples:[Person] = Bundle.main.decode("data.json")
var body: some View {
let columns = [
GridItem(.flexible(minimum: 300), spacing: 10)
]
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.self) { person in
PersonView(name: person.Name, age: person.Age)
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}
The child view is bellow. I suspect the scroll view is stealing the user input, but I am not sure why or how to overcome it. Some buttons are tapeable and some are not.
struct PersonView: View {
#Environment(\.colorScheme) var colorScheme
var name: String
var age: Int
var body: some View {
VStack(alignment:.leading) {
Image("randoPerson")
.resizable()
.scaledToFill()
.frame(minWidth: nil, idealWidth: nil,
maxWidth: UIScreen.main.bounds.width, minHeight: nil,
idealHeight: nil, maxHeight: 300, alignment: .center)
.clipped()
VStack(alignment: .leading, spacing: 6) {
Text("name")
.fontWeight(.heavy)
.padding(.leading)
Text("Age \(age)")
.foregroundColor(Color.gray)
.padding([.leading,.bottom])
Button(action: { print("I was tapped") }) {
HStack {
Image(systemName: "message.fill")
.font(.title)
.foregroundColor(.white)
.padding(.leading)
Text("Message them")
.font(.subheadline)
.foregroundColor(.white)
.padding()
}
.background(Color.blue)
}
.padding()
}
.background(Color(UIColor.systemBackground).cornerRadius(15))
.shadow(color:colorScheme == .dark
? Color.white.opacity(0.2)
: Color.black.opacity(0.2),
radius: 7, x: 0, y: 2)
}
}
}

To fix the issue, you can add an id: UUID associated to a Person and iterate between the Person, inside the ForEach using their ID.
You will also noticed that I added the value as lowercases to respect the Swift convention.
struct Person {
let id: UUID // Add this value
var name: String
var age: Int
}
So here is the ContentView with the id replacing \.self:
struct ContentView: View {
let peoples: [Person] = Bundle.main.decode("data.json")
var body: some View {
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.id) { person in // id replace \.self here
PersonView(name: person.name, age: person.age) // removed uppercase
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}

Related

SwiftUI: using MatchedGeometryEffect for a scrollView?

I am trying to understand MatchGeometryEffect a little better. I am trying to put it on each cell of a list in a scrollView. I want it to be where if showAlternate is false, the main cell is show but if showAlternate is true , then the individual cell will show the else of the conditional which would be a larger cell ( not part of the code yet). I have the matchedGeometryEffect on the ForEach right now but how can I do each cell indvidually?
struct ContentView: View {
#Binding var countries: [Country]
#State var showAlternate = false;
#Namespace var namespace;
var body: some View {
NavigationView {
ScrollView {
ForEach(Array(countries.enumerated()), id: \.1.id) { (index,country) in
LazyVStack {
HStack {
NavigationLink(
destination: CountryView(country: country),
label: {
HStack {
Image(country.image)
.resizable()
.scaledToFit()
Text(country.display_name)
.foregroundColor(Color.black)
.padding(.leading)
Spacer()
}
.padding(.top, 12.0)
.frame(height: 80)
}
).buttonStyle(FlatLinkStyle())
}
.padding(.horizontal, 16.0)
.overlay(Divider(), alignment: .top)
}
}
.background(Rectangle().opacity(0.2).matchedGeometryEffect(id: "shape", in: namespace))
}
}
.font(Font.custom("Avenir", size: 22))
}
}

Style the picker in SwiftUI

I'm doing a login form in Swiftui, however when i try to add a form that contain picker for age the style get messed up and since im new to SwiftUI i didn't know how can i fix this problem?
How can i fix the picker style so it addapte with the Nickname field?
here it is the code that i tried:
#State var nickName: String = ""
#State var age: Int = 18
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
TextField("Nickname", text: $nickName)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
Form {
Section {
Picker(selection: $age, label: Text("Picker")) {
ForEach(18 ..< 99, id: \.self) { index in
Text("\(index)").tag(1)
}
}
}
}
}
// Age
// Country
// Male/Female
}
.background(BlurView(blurType: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.offset(y: 400)
Not sure if you are still working on this project, but here is a neat workaround:
struct ContentView: View {
#State private var age = 18
var body: some View {
NavigationView {
VStack {
Divider()
HStack {
NavigationLink(destination: AgePickerView(age: $age)) {
HStack {
Text("Age")
Spacer()
Text("\(age) >")
.padding(.trailing)
}
}
}
.foregroundColor(.gray)
Divider()
}
.padding(.leading, 80)
}
}
struct AgePickerView: View {
#Binding var age: Int
var body: some View {
List(18..<99) { index in
Button(action: {
self.age = index
}) {
HStack {
Text("\(index)")
Spacer()
if index == age {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
}
}
To get the same NavigationView style but remove the Form style, you need to wrap a NavigationLink in a NavigationView.
The little > sign I put at the end of the Text view in the NavigationLink doesn't perfectly match the one in the Form style, so you can look around for an image that is more similar if you would like.
The AgePickerView is pretty straightfoward; it's just a list of buttons that are modifying a binding to the original state variable in ContentView through tap gestures. The checkmark is there to imitate a Picker view.
The best part of this is that you don't have to worry about all that index shifting with a picker.

Error: Argument passed to call that takes no arguments SwiftUI

In my code I get this error and I cannot figure out how to resolve it. The error is in the Trailing block of code
struct Map: View {
#ObservedObject public var dataManager = DataManager.shared
#Binding public var seqId: String
// rz- to pass seqId to second view
// #State public var seqId2: String = ""
var body: some View {
ZStack {
VStack {
Text("Results").foregroundColor(Color.white).onAppear{self.dataManager.downloadSeqData(seqId: self.seqId)} .onAppear(perform: playSound)/*rz want to add this sound func to contentview somehow, otherwise it will constantly play sound when page loads */ .edgesIgnoringSafeArea(.all)
//rz- if strand is linear draw line
if dataManager.dataSet?.INSDSeq.topology == "linear" {
Rectangle()
.fill(Color.black)
.frame(width: 500, height: 20)
}
//rz- if strand is circular draw o
if dataManager.dataSet?.INSDSeq.topology == "circular" {
Circle()
.stroke(Color.black, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
.frame(width: 500, height: 500)
}
if dataManager.dataSet?.INSDSeq.topology != "circular" && dataManager.dataSet?.INSDSeq.topology != "linear"{
Text("Data Unavailable, try a new accession number.")
}
}
.navigationBarItems(
//rz - added home and genbank view to navigation bars
leading:
NavigationLink(destination: ContentView()) {
Text("New Query")
.font(.title)
.foregroundColor(Color.black)
},
trailing:
NavigationLink(destination: Screen(seqId: self.$seqId)) {
Text("GenBank View")
.font(.title)
.foregroundColor(Color.black)
Error is in $seqId
NavigationLink(destination: Screen(seqId: self.$seqId)) {
Here is the Screen view
import SwiftUI
struct Screen: View {
#ObservedObject public var dataManager = DataManager.shared
#State private var seqId: String = ""
var body: some View {
VStack {
TextField("Enter Accession Number", text: $seqId)
.padding()
Button("Search")
{
dataManager.downloadSeqData(seqId: seqId)
}
ScrollView {
VStack(alignment: .leading) {
Text(dataManager.dataSet?.INSDSeq.locus ?? "")
.padding()
Text(dataManager.dataSet?.INSDSeq.organism ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.source ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.taxonomy ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.topology ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.length == nil ? "" : "\(dataManager.dataSet!.INSDSeq.length)") .padding()
Text(dataManager.dataSet?.INSDSeq.strandedness ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.moltype ?? "").padding()
Text(dataManager.dataSet?.INSDSeq.sequence ?? "").padding()
}
.cornerRadius(10)
VStack(alignment: .leading, spacing: 10) {
ForEach(dataManager.featureList, id:\.self) { feature in
VStack (alignment: .leading){
VStack (alignment: .leading) {
Text(feature.INSDFeature_key).bold()
Text(feature.INSDFeature_location)
}.padding()
IntervalSection(feature: feature)
QualsSection(feature: feature)
}
.cornerRadius(10)
}
}
.cornerRadius(20)
}
.padding(.horizontal)
}
}
}
struct IntervalSection: View {
var feature: INSDFeature
var body: some View {
VStack (alignment: .leading){
ForEach(0..<feature.INSDFeature_intervals.count, id: \.self) { i in
ForEach(0..<feature.INSDFeature_intervals[i].INSDInterval.count, id: \.self) { j in
if let from = feature.INSDFeature_intervals[i].INSDInterval[j].INSDInterval_from {
VStack (alignment: .leading){
Text("\(from)").bold()
Text("\(feature.INSDFeature_intervals[i].INSDInterval[j].INSDInterval_to ?? -1)")
}
.padding()
.cornerRadius(10)
}
}
}
}
}
}
struct QualsSection: View{
var feature: INSDFeature
var body: some View {
VStack (alignment: .leading){
ForEach(0..<feature.INSDFeature_quals.count , id: \.self) { i in
ForEach(0..<feature.INSDFeature_quals[i].INSDQualifier.count , id: \.self) { j in
VStack (alignment: .leading){
Text(feature.INSDFeature_quals[i].INSDQualifier[j].INSDQualifier_name).bold()
Text(feature.INSDFeature_quals[i].INSDQualifier[j].INSDQualifier_value)
}
.padding()
.cornerRadius(10)
}
}
}
}
}
In SwiftUI you should use Binding instead of State in cases, where the data is coming from another view. Furthermore both attributes of Screen are private, therefore they can't be accessed in this case set from outside of the struct itself.
I would suggest to make seqID in Screen public like you did it with dataManager and change it to State

SwiftUI - How to close a fake Modal View - from the second view inside it, with a close button?

I have a tricky design. I'm trying to have a close button that from whatever views, inside a "fake" Modal View (fake cause it's full screen, thanks to a code that i found online), it's gonna close the Modal View.
Right now the close button is working in the first view that open in the modal view, but I need it to work also in the next views of the modal view, cause I have a flow inside this modal view to create an item.
This is the View from which I start ColorNewItemView, my fake Modal View.
struct RecapItemToAdd: View {
#State var isPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Button(action: { withAnimation { self.isPresented.toggle()}}) {
Image(systemName: "pencil")
.resizable()
.renderingMode(.original)
.frame(width: 13, height: 17)
.foregroundColor(UIManager.hLightGrey)
}
}
ZStack {
VStack(alignment: .leading) {
ColorNewItemView(isPresenteded: self.$isPresented)
Spacer()
}
}
.background(Color.white)
.edgesIgnoringSafeArea(.all)
.offset(x: 0, y: self.isPresented ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
}
}
}
Note: I know that "keyWindow" is deprecated but I don't know how to change it.
With ColorNewItemView starts my full screen Modal View. In this view the close button works.
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresenteded: Bool
var body: some View {
NavigationStackView {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("First View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: Color2NewItemView(isPresenteded: self.$isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Avanti")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
}
}
}
}
Now I have the next view inside the Modal view, where the close button starts to stop working.
struct Color2NewItemView: View {
#Binding var isPresenteded: Bool
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("Second View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: FabricNewItemView(isPresenteded: $isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Tessuto")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
Spacer()
.frame(height: 18)
PopView{
Text("Back")
.font(UIManager.einaBodySemibold)
.foregroundColor(UIManager.hGrey)
}
}
}
}
Ps.
I had also to use a library called NavigationStack, since I have a custom back button on the bottom of the page, and the Navigation View doesn't let me pop back without using the back in the navigation bar.
Binding can be lost on deep view hierarchy, so it is more appropriate to operate with it on the level it was received.
Here is possible approach using EnvironmentKey (by same idea as presentationMode works)
Introduce helper environment key which holds some closure
struct DismissModalKey: EnvironmentKey {
typealias Value = () -> ()
static let defaultValue = { }
}
extension EnvironmentValues {
var dismissModal: DismissModalKey.Value {
get {
return self[DismissModalKey.self]
}
set {
self[DismissModalKey.self] = newValue
}
}
}
so in your top modal view you can inject into hierarchy callback to dismiss
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresented: Bool
var body: some View {
NavigationStackView {
// ... other code
}
.environment(\.dismissModal, { self.isPresented = false} ) // << here !!
}
}
thus this environment value now is available for all subviews, and you can use it as
struct Color2NewItemView: View {
#Environment(\.dismissModal) var dismissModal
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.dismissModal() // << here !!
}) {
// ... other code
}
}

How to make a button clickable to another view controller only with textfield

I have this scenario that if the user clicks the button then another view controller will popuped which includes only a textView where the user can write something inside.
You can use the the .sheet modifier to display a modal / popup like view in SwiftUI.
struct ContentDetail: View {
struct Item {
let uuid = UUID()
let value: String
}
#State private var items = [Item]()
#State private var show_modal = false
var lectureName:String
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
self.show_modal.toggle()
}, label: {
Text("✏️")
.font(.system(.largeTitle))
.frame(width: 77, height: 70)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 5,
x: 3,
y: 3)
}
}
}
.sheet(isPresented: self.$show_modal) {
CustomModalView()
}
}
}
struct CustomModalView: View {
#State private var text = ""
var body: some View {
TextField("test", text: $text)
.padding(5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 60, design: .default))
.multilineTextAlignment(.center)
}
}
You can read more about it here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/