// // ContentView.swift // gloss // // Created by Saint on 10/23/22. // import GRDB import GRDBQuery import SwiftUIIntrospect import os import SwiftUI import WrappingHStack let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network") var currentId: String? var currentOffset: CGFloat? var gTracker: VisibilityTracker? var printCount: Int64 = 0 var disableDrop = false // var curBook = "John" public extension UserDefaults { func optionalInt(forKey defaultName: String) -> Int? { let defaults = self if let value = defaults.value(forKey: defaultName) { return value as? Int } return nil } func optionalBool(forKey defaultName: String) -> Bool? { let defaults = self if let value = defaults.value(forKey: defaultName) { return value as? Bool } return nil } } func goToRibbon(selectedRibbon: Ribbon, destRibbon: Ribbon, scrollId: Binding, scrollOffset: Binding, refresh: Binding, showOverlay: Binding, appDatabase: AppDatabase, loading: Bool) { Task { // print("SELECTED RIBBON", selectedRibbon) var scrollOffsetToSave = currentOffset var scrollIdToSave = currentId var updatedRibbon = selectedRibbon if selectedRibbon.id != destRibbon.id! || loading { print("switching ribbons") // withAnimation(.spring(response: 0.05)) { // showOverlay.wrappedValue = true showOverlay.wrappedValue = true // } if loading { currentId = destRibbon.scrollId // currentOffset = CGFloat(destRibbon.scrollOffset) } scrollId.wrappedValue = destRibbon.scrollId // print("setting scroll offset") scrollOffset.wrappedValue = CGFloat(destRibbon.scrollOffset) // print(scrollOffset.wrappedValue) // print("end setting scroll offset") refresh.wrappedValue.toggle() var updateSelectRibbon = SelectedRibbon(id: Int64(1), ribbonId: destRibbon.id!) // print("Saving selected ribbon") // print(updateSelectRibbon) do { _ = try await appDatabase.saveSelectedRibbon(&updateSelectRibbon) } catch { // Print("something wrong") } } if !loading { print("not loading") updatedRibbon.scrollOffset = Int(floor(scrollOffsetToSave!)) updatedRibbon.scrollId = scrollIdToSave! _ = try await appDatabase.saveRibbon(&updatedRibbon) // print("saved updatedRibbon", updatedRibbon) // print("UPDATED") // scrollOffsetToSave = userDefaults.object(forKey: "currentOffset") as? CGFloat // scrollIdToSave = userDefaults.object(forKey: "currentId") as? String } else { print("loading") } // print("scrollOffsetToSave: ", scrollOffsetToSave) // print("scrollIdToSave: ", scrollIdToSave) } } extension View { @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Transform) -> some View { if condition { transform(self) } else { self } } } struct RibbonCrown: View { var ribbon: Ribbon @Binding var scrollId: String? @Binding var scrollOffset: CGFloat? @Binding var showOverlay: Bool @Binding var refresh: Bool var draggedRibbon: Ribbon? var isDragging: Bool var height = CGFloat(45) var xOffset = CGFloat(25) var scale = 0.65 @Environment(\.appDatabase) private var appDatabase @Query(SelectedRibbonRequest()) private var sr: [Ribbon] @State var saveOffset = CGFloat() var body: some View { ZStack { MyIcon().frame( width: CGFloat(100 * 1.66 * scale), height: CGFloat(100 * scale), alignment: .center ).foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4))) .if(draggedRibbon != nil && draggedRibbon!.id == ribbon.id && isDragging) { $0.overlay(Color(red: 0.1, green: 0.1, blue: 0.1)) } // .offset(x: 10) Text(ribbon.title) .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) // .foregroundColor(.white)) // .foregroundColor(.black) .frame(minWidth: CGFloat(70), maxWidth: CGFloat(70), minHeight: height, maxHeight: height, alignment: .center) .if(draggedRibbon != nil && draggedRibbon!.id == ribbon.id && isDragging) { $0.overlay(Color(red: 0.1, green: 0.1, blue: 0.1)) } // .if(!isDragging || draggedRibbon == nil || draggedRibbon!.id != ribbon.id) { $0.background(Color(red: 0.1, green: 0.1, blue: 0.1)) } // .if(draggedRibbon != nil && draggedRibbon!.id == ribbon.id) { $0.background(.red) } .background(Color(red: 0.1, green: 0.1, blue: 0.1)) // .offset(x: 10) // .background(Color(red: 0.1, green: 0.1, blue: 0.1)) // .background(.red) // .background(.yellow) .multilineTextAlignment(.center) // .minimumScaleFactor(0.5) // .padding([.top, .bottom], 10) .font(Font.custom("AveriaSerifLibre-Regular", size: CGFloat(10))) } .frame(width: CGFloat(100 * 1.66 * scale + 10), height: CGFloat(100 * scale + 5)) } } class Verse: NSObject, Codable { var body: String var verse: Int } func makeVerseView(seg: SegDenorm) -> some View { var retView = Text("") var segSplit = seg.body.components(separatedBy: ";;") let decoder = JSONDecoder() for (index, item) in segSplit.enumerated() { let verse = try! decoder.decode(Verse.self, from: item.data(using: .utf8)!) retView = retView + Text(String(verse.verse)) .font(Font.custom("AveriaSerifLibre-Regular", size: 6)) .baselineOffset(6.0) .foregroundColor(Color.white) retView = retView + Text(verse.body) .foregroundColor(Color.white) .font(Font.custom("AveriaSerifLibre-Regular", size: 15)) } return retView } private struct SegRow: View { var seg: SegDenorm var ribbonId: Int64 var body: some View { var retView = Text("") // .onTapGesture { // // selectedLine = seg.id // // Print(selectedLine) // Print("meow") // // Print(verse.body) // } var segSplit = seg.body.components(separatedBy: ";;") let decoder = JSONDecoder() // Text(segSplit[0].body) // .id(seg.id) // VStack { // ForEach(segSplit.indices) { i in segSplit.enumerated().forEach { _, item in let verse = try! decoder.decode(Verse.self, from: item.data(using: .utf8)!) var attributedString: AttributedString { // var result = AttributedString("Hello World!") var result = AttributedString(verse.body) // result.underlineStyle = Text.LineStyle( // pattern: .dot, color: .white) return result } retView = retView + Text(String(ribbonId)) // retView = retView + Text(attributedString) // Text(seg.body) // .contentShape(Rectangle()) .font(Font.custom("AveriaSerifLibre-Regular", size: 10)) .baselineOffset(6.0) .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) retView = retView + Text(attributedString) .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) .font(Font.custom("AveriaSerifLibre-Regular", size: 20)) // .frame(maxWidth: .infinity, alignment: .leading) // .contentShape(Rectangle()) // .listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2)) // .listRowInsets(EdgeInsets()) // .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) // .listRowSeparator(.hidden) // .id(String(seg.id) + "body" + String(i)) // .id(seg.id) // .listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2)) // .listRowInsets(EdgeInsets()) // .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) // .listRowSeparator(.hidden) // .id(String(seg.id) + "verse" + String(i)) // .id(seg.id) // .onTapGesture { // selectedLine = seg.id // Print(selectedLine) // } } // } let myText = "There was a man sent from God, whose name was John. This man came for a witness, to give testimony of the light, that all men might believe through him. He was not the light, but was to give testimony of the light. That was the true light, which enlighteneth every man that cometh into this world. He was in the world, and the world was made by him, and the world knew him not." let arrayOfText = myText.components(separatedBy: " ") var wordSelected = "" var newView = WrappingHStack(alignment: .leading, horizontalSpacing: 3.5) { ForEach(0.. var selectedRibbon: [Ribbon] // @Query(RibbonRequest(id: Int64(UserDefaults.standard.optionalInt(forKey: "lastRibbonId") ?? 1))) private var selectedRibbon: [Ribbon] init() { UITableView.appearance().backgroundColor = UIColor(Color(red: 0.2, green: 0.2, blue: 0.2)) _selectedRibbon = Query(SelectedRibbonRequest()) // self._scrollDelegate = State(initialValue: ScrollViewHandler()) } var body: some View { // Print("rendering") var size1 = CGFloat(11) var size2 = CGFloat(100) var fontSize = CGFloat(15) var scale = 0.65 var height = CGFloat(50) var xOffset = CGFloat(25) var width = CGFloat(100 * 1.66 * scale) // ForEach(ribbons) { ribbon in // data.append(ribbon) // } GeometryReader { geometry in ZStack(alignment: .top) { VStack(alignment: .leading) { VStack { // ReorderableForEach($data, allowReordering: $reorder) { item, isDragged in ForEach(ribbons) { ribbon in RibbonCrown(ribbon: ribbon, scrollId: $scrollId, scrollOffset: $scrollOffset, showOverlay: $showOverlay, refresh: $refresh, draggedRibbon: draggedRibbon, isDragging: isDragging) .onDrag { self.draggedRibbon = ribbon return NSItemProvider() } .onDrop(of: [.item], delegate: DropViewDelegate(destinationItem: ribbon, draggedItem: $draggedRibbon, isDragging: $isDragging, appDatabase: appDatabase)) .offset(x: 6, y: 6) } // .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) } .frame(width: geometry.size.width, height: geometry.size.height - 100, alignment: .topLeading) .background(Color(red: 0.1, green: 0.1, blue: 0.1)) .zIndex(0) .animation(.default, value: ribbons) .onDrop(of: [.item], delegate: DropViewDelegate2(isDragging: $isDragging)) // Spacer() // ForEach(ribbons) { ribbon in // RibbonCrown(ribbon: ribbon, // scrollId:$scrollId, // scrollOffset:$scrollOffset, // showOverlay: $showOverlay, // refresh:$refresh // ) // // .buttonStyle(BlueButtonStyle()) // // .frame(alignment: .topLeading) // // .background(Color(red: 0.4, green: 0.4, blue: 0.1)) // .animation(nil) // } } .background(Color(red: 0.1, green: 0.1, blue: 0.1)) .frame(alignment: .topLeading) // .animation(nil) VStack { ScrollViewReader { proxy in VisibilityTrackingScrollView(action: handleVisibilityChanged) { // ScrollView { LazyVStack { ForEach(segs) { seg in SegRow(seg: seg, ribbonId: selectedRibbon[0].id!) .id("\(seg.id)") .offset(x: -dragOffset) // .offset(x: pulledOut.width) .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) .trackVisibility(id: "\(seg.id)") // .onChange(of: geometry.frame(in: .named("scrollView"))) { imageRect in // Print(imageRect) // Print(outerProxy) // if isInView(innerRect: imageRect, isIn: outerProxy) { // visibleIndex.insert(item) // } else { // visibleIndex.remove(item) // } // } } } .background(Color(red: 0.18, green: 0.18, blue: 0.18)) } .onAppear { Print("APPEAR") // Print(selectedRibbon[0]) // scrollId = "3" // scrollOffset = 103 // refresh.toggle() goToRibbon(selectedRibbon: selectedRibbon[0], destRibbon: selectedRibbon[0], scrollId: $scrollId, scrollOffset: $scrollOffset, refresh: $refresh, showOverlay: $showOverlay, appDatabase: appDatabase, loading: true) } .onChange(of: refresh) { _ in // if let target = target { // gTracker!.visibleViews["123123"] = CGFloat(100) // Print("ON CHANGE", gTracker!.visibleViews) // Print("removing", gTracker!.visibleViews.removeAll()) Task { DispatchQueue.main.async { Print("scroll Id target: \(scrollId)") proxy.scrollTo(scrollId!, anchor: .top) // proxy.scrollTo(String(Int(scrollId!)! + 1)) // currentId = scrollId! // if (currentId != scrollId!) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { Print(" scroll id target", scrollId) Print(" current id ", currentId) Print(gTracker!.sortedViewIDs) if currentId! != scrollId! { Print("NO MATCH") } // Print(" scroll offset target", scrollOffset) Print(" current offset ", gTracker!.visibleViews[scrollId!]) var curOffset = gTracker!.visibleViews[scrollId!] Print(" stats", gTracker!.visibleViews) // // setScrollOffset = CGFloat(Int(currentOffset!) * -1 + Int(scrollOffset!)) if curOffset != nil { setScrollOffset = CGFloat(Int(scrollOffset!) - Int(curOffset!)) Print("applying scroll offset \(setScrollOffset)") // // setScrollOffset = CGFloat(Int(scrollOffset!)) // Print("setting scroll offset", setScrollOffset) refresh2.toggle() } else { var adjust = (Int(scrollId!)! - Int(currentId!)!) * 200 Print("adjusting \(adjust)") setScrollOffset = CGFloat(adjust) refresh.toggle() } // // currentId = scrollId! // DispatchQueue.main.async { // currentOffset = scrollOffset! // currentId = scrollId! // } } } } } .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in Print("introspect") // Print("Scroll delegate offset", scrollDelegate.scrollOffset) if setScrollOffset != nil { // Print("Setting scroll offset in introspect", setScrollOffset) DispatchQueue.main.async { scrollView.contentOffset.y = scrollView.contentOffset.y + setScrollOffset! setScrollOffset = nil // self.showOverlay = false withAnimation { showOverlay = false } } } // if (thisScrollView == nil) { // Print("init scroll") // thisScrollView = scrollView // scrollView.contentOffset.y = CGFloat(selectedRibbon[0].scrollOffset) // } Print("end introspect") } .listStyle(PlainListStyle()) } .zIndex(1) .background(Color(red: 0.2, green: 0.2, blue: 0.2)) .frame(width: geometry.size.width - 50, height: geometry.size.height / 2 - vertSep) .offset(x: 30, y: 0) .offset(x: pulledOut.width) .offset(x: viewState.width, y: viewState.height) .gesture( DragGesture() .onChanged { gesture in if endedDrag { endedDrag = false scrollOffset = readOffset.y - 20 // _ = Print("meow") } // logger.error("hello222") // NSLog("hellooo") Print(viewState.width) if abs(gesture.translation.width) > 20 { viewState.width = gesture.translation.width if gesture.translation.width < -50, pulledOut.width == CGFloat(0) { dragOffset = gesture.translation.width + 50 } } // offset.y = gesture.translation.width // logger.log("hello") } .onEnded { _ in endedDrag = true var pulledOutWidth = CGFloat(0) if viewState.width < 0 { pulledOutWidth = CGFloat(0) } else if abs(viewState.width + pulledOut.width) > 30 { pulledOutWidth = CGFloat(200) } withAnimation(.spring(response: 0.2)) { pulledOut.width = pulledOutWidth viewState = .zero dragOffset = .zero } } ) Text("separator").foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) .gesture( DragGesture() .onChanged { gesture in vertSep = vertSep - gesture.translation.height Print(gesture.translation.width) Print(gesture.translation.height) Print("drag") // if (endedDrag) { // endedDrag = false // scrollOffset = readOffset.y - 20 // // _ = Print("meow") // } //// logger.error("hello222") //// NSLog("hellooo") // Print(viewState.width) // if (abs(gesture.translation.width) > 20) { // viewState.width = gesture.translation.width // } ////offset.y = gesture.translation.width //// logger.log("hello") } // .onEnded { _ in // endedDrag = true // var pulledOutWidth = CGFloat(0) // if (viewState.width < 0) { // pulledOutWidth = CGFloat(0) // } // else if abs(viewState.width + pulledOut.width ) > 30 { // pulledOutWidth = CGFloat(200) // } // withAnimation(.spring(response: 0.2)) { // pulledOut.width = pulledOutWidth // viewState = .zero // } // } ) ScrollViewReader { _ in VisibilityTrackingScrollView(action: handleVisibilityChanged2) { // ScrollView { LazyVStack { ForEach(segs) { seg in SegRow(seg: seg, ribbonId: selectedRibbon[0].id!) .id("\(seg.id)") .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) .trackVisibility(id: "\(seg.id)") // .onChange(of: geometry.frame(in: .named("scrollView"))) { imageRect in // Print(imageRect) // Print(outerProxy) // if isInView(innerRect: imageRect, isIn: outerProxy) { // visibleIndex.insert(item) // } else { // visibleIndex.remove(item) // } // } } } .background(Color(red: 0.18, green: 0.18, blue: 0.18)) } .onAppear { Print("APPEAR") // Print(selectedRibbon[0]) // scrollId = "3" // scrollOffset = 103 // refresh.toggle() // goToRibbon(selectedRibbon: selectedRibbon[0], // destRibbon: selectedRibbon[0], // scrollId: $scrollId, // scrollOffset: $scrollOffset, // refresh: $refresh, // showOverlay: $showOverlay, // appDatabase: appDatabase, // loading: true) } // .onChange(of: refresh) { target in // //if let target = target { // //gTracker!.visibleViews["123123"] = CGFloat(100) // // Print("ON CHANGE", gTracker!.visibleViews) // // Print("removing", gTracker!.visibleViews.removeAll()) // Task { // DispatchQueue.main.async { // Print("scroll Id target: \(scrollId)") // proxy.scrollTo(scrollId! , anchor: .top) // // proxy.scrollTo(String(Int(scrollId!)! + 1)) // // currentId = scrollId! // // if (currentId != scrollId!) { // DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // Print(" scroll id target", scrollId) // Print(" current id ", currentId) // Print(gTracker!.sortedViewIDs) // if (currentId! != scrollId!) { // Print("NO MATCH") // } // // Print(" scroll offset target", scrollOffset) // Print(" current offset ", gTracker!.visibleViews[scrollId!]) // var curOffset = gTracker!.visibleViews[scrollId!] // Print(" stats", gTracker!.visibleViews) // // // setScrollOffset = CGFloat(Int(currentOffset!) * -1 + Int(scrollOffset!)) // if (curOffset != nil) { // setScrollOffset = CGFloat(Int(scrollOffset!) - Int(curOffset!)) // Print("applying scroll offset \(setScrollOffset)") // // // setScrollOffset = CGFloat(Int(scrollOffset!)) // // Print("setting scroll offset", setScrollOffset) // refresh2.toggle() // } else { // var adjust = (Int(scrollId!)! - Int(currentId!)!) * 200 // Print("adjusting \(adjust)") // setScrollOffset = CGFloat(adjust) // refresh.toggle() // } // // // currentId = scrollId! // // DispatchQueue.main.async { // // currentOffset = scrollOffset! // // currentId = scrollId! // // } // } // } // } // } // .introspectScrollView { scrollView in // Print("introspect") // // scrollView.delegate = scrollDelegate // //Print("Scroll delegate offset", scrollDelegate.scrollOffset) // if (setScrollOffset != nil) { // // Print("Setting scroll offset in introspect", setScrollOffset) // DispatchQueue.main.async { // scrollView.contentOffset.y = scrollView.contentOffset.y + setScrollOffset! // setScrollOffset = nil // withAnimation { // showOverlay = false // } // } // } // // if (thisScrollView == nil) { // // Print("init scroll") // // thisScrollView = scrollView // // scrollView.contentOffset.y = CGFloat(selectedRibbon[0].scrollOffset) // // } // Print("end instrospect") // } .listStyle(PlainListStyle()) } .zIndex(1) .background(Color(red: 0.2, green: 0.2, blue: 0.2)) .frame(width: geometry.size.width - 50) .offset(x: 30, y: 0) .offset(x: pulledOut.width) .offset(x: viewState.width, y: viewState.height) .gesture( DragGesture() .onChanged { gesture in if endedDrag { endedDrag = false scrollOffset = readOffset.y - 20 // _ = Print("meow") } // logger.error("hello222") // NSLog("hellooo") Print(viewState.width) if abs(gesture.translation.width) > 20 { viewState.width = gesture.translation.width } // offset.y = gesture.translation.width // logger.log("hello") } .onEnded { _ in endedDrag = true var pulledOutWidth = CGFloat(0) if viewState.width < 0 { pulledOutWidth = CGFloat(0) } else if abs(viewState.width + pulledOut.width) > 30 { pulledOutWidth = CGFloat(200) } withAnimation(.spring(response: 0.2)) { pulledOut.width = pulledOutWidth viewState = .zero } } ) } if showOverlay { Rectangle() .frame(width: geometry.size.width - 50, height: geometry.size.height + 200) .background(.ultraThinMaterial) // .blur(radius: 0.8) .offset(x: 30, y: -100) .opacity(0.98) .transition(.opacity) // .frame(width: geometry.size.width - 50) .offset(x: pulledOut.width) .offset(x: viewState.width, y: viewState.height) .zIndex(2) } } } } func handleVisibilityChanged2(_: String, change _: VisibilityChange, tracker _: VisibilityTracker) {} func handleVisibilityChanged(_: String, change _: VisibilityChange, tracker: VisibilityTracker) { // var printRate: Int64 = 10 gTracker = tracker // @Environment(\.appDatabase) var appDatabase // switch change { // case .shown: print("\(id) shown") // case .hidden: print("\(id) hidden") // } // if (printCount % printRate == 0) { // print("VISIBILITY CHANGED STARTED") // print(tracker.visibleViews) // print(tracker.sortedViewIDs) // print("VISIBILITY CHANGED ENDED") // } // printCount += 1 // if (currentId != nil) { // currentOffset = tracker.visibleViews[currentId!]! // } let visibleViews2 = Array(tracker.visibleViews.keys) if visibleViews2.count == 0 { return } // currentId = tracker.sortedViewIDs[tracker.sortedViewIDs.count - 1] currentId = tracker.sortedViewIDs[0] currentOffset = tracker.visibleViews[currentId!]! // if (Int(currentOffset!) != -1 && Int(currentOffset!) < 0) { // if (tracker.sortedViewIDs.count > 1) { // currentId = tracker.sortedViewIDs[1] // currentOffset = tracker.visibleViews[currentId!]! // } // } // if (currentId != nil) { // // if (printCount % printRate == 0) { // // print(printCount) // // print("cat current ID:", currentId) // // } // // print("got here") // currentOffset = tracker.visibleViews[currentId!]! // } } } private let itemFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .medium return formatter }() struct DropViewDelegate2: DropDelegate { @Binding var isDragging: Bool func dropUpdated(info _: DropInfo) -> DropProposal? { return DropProposal(operation: .move) } func performDrop(info _: DropInfo) -> Bool { isDragging = false return true } func dropEntered(info _: DropInfo) { isDragging = true print("SECOND DROPPPOOO") } } struct DropViewDelegate: DropDelegate { let destinationItem: Ribbon // @Binding var colors: [Color] @Binding var draggedItem: Ribbon? @Binding var isDragging: Bool let appDatabase: AppDatabase func dropUpdated(info _: DropInfo) -> DropProposal? { return DropProposal(operation: .move) } func dropExited(info _: DropInfo) { print("EXITED") isDragging = false } func dropEntered(info _: DropInfo) { Task { isDragging = true if draggedItem == nil { return } if disableDrop { return } var newRibbon = draggedItem! var newDest = destinationItem var oldPos = draggedItem!.pos var newPos = destinationItem.pos print("dragged item") print(draggedItem) print("dest item") print(destinationItem) if draggedItem!.id! == destinationItem.id! { return } newRibbon.pos = destinationItem.pos _ = try await appDatabase.updateRibbonPosition(&newRibbon, oldPos, newPos) disableDrop = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { disableDrop = false } draggedItem!.pos = newPos // draggedItem = nil } } func performDrop(info _: DropInfo) -> Bool { print("PERFORMED DROPPPP") draggedItem = nil isDragging = false return true } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() ContentView().environment(\.appDatabase, .random()) } } extension View { func Print(_ vars: Any...) -> some View { for v in vars { print(v) } return EmptyView() } }