Reputation: 4033
I'm trying to pass data from a ScrollView's component to a modal. In this case, the data I'm trying to pass is image
. My approach was to update a state each time a ShelterView
is clicked.
There is data being passed to ShelterViewDetailed
, but somehow solely the ForEach
-Loop's last item.background
. In other words, no matter what ShelterView
is clicked - always the last item.background
is being displayed.
Any suggestions?
struct HomeList: View {
@State var showContent = false
@State var image = ""
var shelters = SheltersData
var body: some View {
VStack() {
HStack {
HomeListTitle()
Spacer()
}
.padding(.leading, 40)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 35) {
ForEach(shelters) { item in
ShelterView(title: item.title, background: item.background)
.onTapGesture {
self.showContent.toggle()
self.image = item.background
}
.sheet(isPresented: self.$showContent)
{ ShelterDetailedView(image: self.image) }
}
}
.padding(.leading, 40)
.padding(.trailing, 40)
.padding(.bottom, 60)
Spacer()
}
}
}
}
struct ShelterDetailedView: View {
var image: String
var body: some View {
ZStack {
BlurView(style: .extraLight)
VStack {
Image(image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 300)
Spacer()
}
}
}
}
Edit:
struct ShelterView: View {
var title: String?
var background: String?
var body: some View {
VStack {
Text(title ?? "")
.font(Font.custom("Helvetica Now Display Bold", size: 30))
.foregroundColor(.white)
.padding(15)
.lineLimit(2)
Spacer()
}
.background(
Image(background ?? "")
.brightness(-0.11)
.frame(width: 255, height: 360)
)
.frame(width: 255, height: 360)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
}
struct ShelterStruct: Identifiable {
var id: Int
var title: String
var background: String
}
let SheltersData = [
ShelterStruct(id: 1, title: "One", background: "pacific"),
ShelterStruct(id: 2, title: "Two", background: "second"),
ShelterStruct(id: 3, title: "Three", background: "ccc")
]
Upvotes: 5
Views: 2045
Reputation: 1801
Update:
So the major problem here is that your images (I'm betting) are much wider than the 255 width frame you are putting them in. Normally not a problem as you are correctly using framing to restrict the visible size. However, as it turns out, onTapGesture will recognise taps even on the parts of the view that extend outside the visible frame.
In the example below I've refactored your view a bit and you can also see that I'm forcing the contentView of the ShelterView to a Rectangle()
which should wrap nicely around the frame. The onTapGesture goes below this and therefore your taps are constrained to activating the view that lays within the bounds of the Rectangle.
Also notice that I moved the .sheet outside of the ForEach loop. You only need one modal.
My last thought, this was much harder than it had any right to be IMHO. Only by opening the view debugger did I realise that the problem lay in the touch targets and that it wasn't a glitch in SwiftUI or something more sinister. But that being said, I'm using it in what will be a production product and, these things aside, really loving it.
struct HomeList: View {
@State var showContent = false
@State var selectedShelter: ShelterStruct? = nil
var shelters = SheltersData
var body: some View {
VStack() {
HStack {
Text("Home List Title")
Spacer()
}
.padding(.leading, 40)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 35) {
ForEach(shelters, id: \.id) { item in
ShelterView(shelter: item)
.contentShape(Rectangle())
.onTapGesture {
print("Tap")
self.selectedShelter = item
}
}
}
.padding(.leading, 40)
.padding(.trailing, 40)
.padding(.bottom, 60)
Spacer()
}
.sheet(item: self.$selectedShelter) { shelter in
ShelterDetailedView(image: shelter.background)
}
}
}
}
struct ShelterDetailedView: View {
var image: String
var body: some View {
ZStack {
VStack {
Image(image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 300)
Spacer()
}
}
}
}
struct ShelterView: View {
var shelter: ShelterStruct
var body: some View {
VStack {
Text(shelter.title)
.font(Font.custom("Helvetica Now Display Bold", size: 30))
.foregroundColor(.white)
.padding(15)
.lineLimit(2)
Spacer()
}
.background(
Image(shelter.background)
.resizable()
.aspectRatio(1, contentMode: .fill)
.brightness(-0.11)
.frame(width: 255, height: 360)
)
.frame(width: 255, height: 360)
.cornerRadius(30)
.shadow(color: .gray, radius: 10, x: 0, y: 10)
}
}
struct ShelterStruct: Identifiable {
var id: Int
var title: String
var background: String
}
let SheltersData = [
ShelterStruct(id: 1, title: "One", background: "pacific"),
ShelterStruct(id: 2, title: "Two", background: "second"),
ShelterStruct(id: 3, title: "Three", background: "ccc")
]
It can take a while to get used to the way that SwiftUI works. What's happening here is that you are changing your @State image within your ForEach loop which is being evaluated when the body is instantiated. This means that your state will quickly loop through each of the available states and settle on the final image in your set of shelters.
Then when you tap any of the rows, you toggle your Bool and the sheet appears with the current image state, which will be the last one.
What you need to do is have an optional @State for the selectedImage:
@State private var selectedImage: String? = nil
Then you can use this form of sheet:
.sheet(item: $selectedImage) {
And to show it you have this:
.onTapGesture {
self.selectedImage = item.background
}
Note that in this you are using the closure variable from the ForEach loop for item instead of using a @State.
When the sheet is dismissed, SwiftUI will update the bound selectedImage back to nil for you, thus keeping the state of your view correct.
Upvotes: 3
Reputation: 11531
I think I found the issue you have.
// ForEach(shelters) { item in
I don't know what the value is here : SheltersData.
But usually, when people use some array values, they prefer using like the following:
ForEach(shelters, id: \.title) { item in
You are supposed to use id
to identify the items.
struct SheltersData{
var title: String
var background: String
}
var shelters : [SheltersData] = [SheltersData(title: "cool", background: "image2"),
SheltersData(title: "hot", background: "image1")]
If both titles are same, you will always see last background as you see before.
Therefore, I suggest you add different id
to your ForEach structures.
Upvotes: 0