I have come across a very unfortunate problem. I have constructed a view in SwiftUI and it is working perfectly, however only if it is the root view. Navigating to it via NavigationLink leads to a weird problem where only the elements that are visible when the view appears, work. Every element in the ScrollView that is further down than what is initially available doesn't behave correctly.
Here is the code (simplified for easier reading:
import SwiftUI
struct CameraModelPopupView: View {
@Binding var isPresented: Bool
@Binding var selectedCamera: CameraModel?
@State var addingCamera: Bool = false
@State private var searchText = ""
@State var cameraModels: [CameraModel] = loadDB("cameras.json")
@State private var selectedItemIndex: Int? = nil
private var filteredCameraModels: [CameraModel] {
//Do rest
if searchText.isEmpty {
return cameraModels
} else {
return cameraModels.filter { $0.modelName.localizedCaseInsensitiveContains(searchText) || $0.manufacturerName.localizedCaseInsensitiveContains(searchText) }
}
}
var body: some View {
NavigationView {
VStack(spacing:0) {
//Search-bar and other
HStack{
Spacer()
SearchBar(text: $searchText)
//.padding(.horizontal)
Button(action: {
addingCamera = true
}) {
Image(systemName: "plus.square.dashed").resizable()
.frame(width: 20, height: 20)
}.padding(.horizontal)
Spacer()
}
.background(Material.ultraThick)
.background(Color(hex: "#2d4269"))
//Main View (problems here)
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 10)]) {
ForEach(filteredCameraModels, id: \.id) { camera in
CardFlip(camera: camera, nw: $nw, isSelected: $isPresented, selectedItemIndex: $selectedItemIndex, selectedCamera: $selectedCamera)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
.background(Material.regularMaterial)
.background(Color(hex: "#2d4269"))
.navigationTitle("Camera Models")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Cancel") {
isPresented = false
}
}
}
}.onAppear(){
let customCameras = loadUserCustomCameras()
cameraModels.append(contentsOf: customCameras)
}
.fullScreenCover(isPresented: $addingCamera){
AddCameraView(cameraModels: $cameraModels, isPresented: $addingCamera)
}
}
}
}
When removing the LazyVGrid, everything works fine, this is what confuses me so much. The CardFlip elements are views that flip to reveal a back side with information about the camera. But with the LazyVGrid only the cards that are visible when accessing the view flip and the lower ones don't. When the view is the root view, all cards flip. When I remove the LazyVGrid, all cards flip.
Any and all help would be greatly appreciated!
EDIT: Here is the CardFlip implementation as requested:
import SwiftUI import CachedAsyncImage
//Main Ansicht die Vorne und Hinten verbindet
struct CardFlip: View {
let camera: CameraModel
@State var backDegree = -90.0
@State var frontDegree = 0.0
@State var isFlipped = false
@Binding var nw: NetworkMonitor
@Binding var isSelected: Bool
@Binding var selectedItemIndex: Int?
@Binding var selectedCamera: CameraModel?
@State var timmre: Timer?
let width : CGFloat = 250
let height : CGFloat = 350
let durationAndDelay : CGFloat = 0.2
var body: some View {
ZStack {
FrontCameraCard(camera:camera, width: width, height: height, degrees: $frontDegree, nw: $nw)
BackCameraCard(camera: camera, width: width, height: height, degrees: $backDegree, isFlipped: $isFlipped, nw: $nw, isSelected: $isSelected, selectedCamera: $selectedCamera)
}.onTapGesture {
selectedItemIndex = camera.id
flipCard ()
}
.onAppear(){
timmre = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true){timer in
if selectedItemIndex != camera.id && isFlipped{
flipCard()
}
}
}
.onDisappear(){
timmre?.invalidate()
timmre = nil
}
}
//Flip funktion
func flipCard () {
isFlipped = !isFlipped
if isFlipped {
withAnimation(.linear(duration: durationAndDelay)) {
frontDegree = 90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
backDegree = 0
}
} else {
withAnimation(.linear(duration: durationAndDelay)) {
backDegree = -90
}
withAnimation(.linear(duration: durationAndDelay).delay(durationAndDelay)){
frontDegree = 0
}
}
}
}
//Ansicht für vorne (Bild und titel)
struct FrontCameraCard: View{
let camera: CameraModel
let width : CGFloat
let height : CGFloat
@Binding var degrees: Double
@Binding var nw: NetworkMonitor
var body: some View{
VStack(alignment: .leading) {
if !UserDefaults.standard.bool(forKey: "LoadOverWifiOnly") || !nw.isCellular || !nw.isConnected{
GeometryReader { geometry in
CachedAsyncImage(url: URL(string: camera.modelImage), urlCache: .shared) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color.white)
}
placeholder: {
ZStack{
Rectangle()
}
}
}
}
else{
//WIP
}
VStack(alignment: .leading){
Text("\(camera.manufacturerName) \(camera.filmOrDigital)")
.font(.system(size: 10))
Text(camera.modelName)
.font(.headline)
}
.padding()
}
.background(Material.ultraThickMaterial)
.cornerRadius(12)
//Das hier muss auf das äußerste view Element/auf die äußerste view gruppe
.rotation3DEffect(Angle(degrees: degrees), axis: (x: 0, y: 1, z: 0))
}
}
//Ansicht hinten
struct BackCameraCard: View{
let camera: CameraModel
let width : CGFloat
let height : CGFloat
@Binding var degrees: Double
@Binding var isFlipped: Bool
@Binding var nw: NetworkMonitor
@Binding var isSelected: Bool
@Binding var selectedCamera: CameraModel?
@State var CameraDetai: Bool = false
// zugehöriger command um die kamera dann feszulegen: selectedCamera = camera
var body: some View{
VStack(alignment: .leading) {
ZStack{
HStack{
Spacer()
Text("Formats")
.font(.headline)
Spacer()
}.padding(10)
HStack{
Spacer()
Button(action: {
CameraDetai = true
}) {
Image(systemName: "info.square.fill")
.foregroundColor(.primary)
}
}.padding(.horizontal)
.opacity(isFlipped ? 0.5 : 0.0)
}
HStack{
Text("Format")
.font(.system(size: 10))
Spacer()
Text("Crop")
.font(.system(size: 10))
Spacer()
Text("Sensor area")
.font(.system(size: 10))
}.padding(.horizontal)
// Spacer()
ScrollView{
LazyVGrid(columns: [GridItem(.flexible())]) {
ForEach(Array(camera.formats.enumerated()), id: \.element) { index, format in
if camera.isBodyOnly {
CardFlipRow(item: .init(
id: index,
title: format[0],
width: Double(format[1])!,
height: Double(format[2])!,
cropFactor: Double(43.2666153/sqrt(pow(Double(format[1])!, 2)+pow(Double(format[2])!, 2)))
), camera: camera, selectedCamera: $selectedCamera, showingSheet: $isSelected)
.shadow(color: Color.black.opacity(0.1), radius: 10, y: 3)
.opacity(isFlipped ? 1 : 0)
.animation(.easeIn(duration: 0.5).delay(Double(0.15 + (0.15*Double(index)))))
}
else{
CardFlipRow(item: .init(
id: index,
title: format[0],
width: Double(format[1])!,
height: Double(format[2])!,
cropFactor: Double(43.2666153/sqrt(pow(camera.sensorWidth, 2)+pow(camera.sensorHeight, 2)))
), camera: camera, selectedCamera: $selectedCamera, showingSheet: $isSelected)
.shadow(color: Color.black.opacity(0.1), radius: 10, y: 3)
.opacity(isFlipped ? 1 : 0)
.animation(.easeIn(duration: 0.5).delay(Double(0.15 + (0.15*Double(index)))))
}
}
}.padding(5)
}
Spacer()
}
// .padding()
.frame(maxWidth: 250, idealHeight: 250, maxHeight: 350)
// .background(Material.regularMaterial)
.background(Material.ultraThickMaterial)
.cornerRadius(12)
// .shadow(color: Color.black.opacity(0.1), radius: 10, y: 3)
// .shadow(color: Color.black.opacity(0.1), radius: 10, y: 3)
//Das hier muss auf das äußerste view Element/auf die äußerste view gruppe
.rotation3DEffect(Angle(degrees: degrees), axis: (x: 0, y: 1, z: 0))
.fullScreenCover(isPresented: $CameraDetai){
NavigationView{
CameraDetail(cameraModel: generic35)
.navigationTitle("Camera Specs")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading: Button(action: {
self.CameraDetai.toggle()
}, label: {
Text("Close").foregroundColor(.red)
}))
}.edgesIgnoringSafeArea(.all)
}
}
}