Core Data: Not fetching entity (one to many relationship) - swiftui

I am new to Core Data and am using a one-to-many relationship for a simple Notes app. A folder can have "many" notes. When a folder is created, notes can be created in that folder. My problem is that when I press the icon that's supposed to add a note, it does nothing. It used to add a note but ever since I attempted a one-to-many relationship amongst folders and notes, it does nothing. I know I must have a logic error somewhere.
To "filter" the notes by folder I passed in an ObservedObject in the Note view (which is the subview of the Folder view). I tried to set up a notes array but I've had no luck. I feel like there's a problem with my Core Dara Properties
FolderView:
struct Sidebar: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.folderName, ascending: true)])
var folders: FetchedResults<Folder>
#State private var selectedFoldersIds: Set<Folder.ID> = []
private var selectedFolder: Folder?{
guard let selectedFolderId = selectedFoldersIds.first,
let selectedFolder = folders.filter ({$0.id == selectedFolderId}).first else{
return nil
}
return selectedFolder
}
var body: some View {
Button(){
isPresented = true
} label: {
HStack{
Image(systemName: "folder.badge.plus")
Text("Add Folder")
}
.cornerRadius(50)
}
.sheet(isPresented: $isPresented){
AddFolderView{ name in
saveNewFolder(name: name)
}
}
.padding()
Divider()
Text("Folders")
.frame(maxWidth: .infinity, alignment: .leading)
.font(.caption)
.foregroundColor(.secondary)
List(folders, selection: $selectedFoldersIds){folder in
HStack{
Image(systemName: "folder")
NavigationLink(folder.folderName!, destination:NoteView(rootFolder: folder))
}
}
.onDeleteCommand(perform: deleteSelectedFolders)
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(action: toggleSidebar) {
Image(systemName: "sidebar.left")
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedFolders){
Label("Delete note", systemImage: "trash")
}
}
}
}
private func toggleSidebar() {
NSApp.keyWindow?.firstResponder?
.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
func saveNewFolder(name: String){
let folder = Folder(context: viewContext)
folder.folderId = UUID()
folder.folderName = name
do{
try viewContext.save()
} catch{
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
private func deleteSelectedFolders(){
withAnimation{
let selectedFolders = folders.filter {selectedFoldersIds.contains($0.id)}
deleteFolders(folders: selectedFolders)
}
}
private func deleteFolders(folders: [Folder]){
viewContext.perform{ folders.forEach(viewContext.delete)}
}
}
Note view:
struct NoteView: View {
#ObservedObject var rootFolder: Folder
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)])
var notes: FetchedResults<Note>
#State private var selectedNotesIds: Set<Note.ID> = []
private var selectedNote: Note?{
guard let selectedNoteId = selectedNotesIds.first,
let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{
return nil
}
return selectedNote
}
var body: some View {
List(rootFolder.notesArray, selection: $selectedNotesIds){ note in // is the problem here?
VStack{
NavigationLink(note.name!, destination: NoteEditor(note: note))
HStack{
Image(systemName: "folder")
Text(rootFolder.folderName!)
}
Divider()
}
}
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(){
isPresented.toggle()
} label: {
Image(systemName: "square.and.pencil")
}
.sheet(isPresented: $isPresented){
AddNoteView{ name in
saveNewNote(name: name, text: "")
}
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedNotes){
Label("Delete note", systemImage: "trash")
}
}
}
}
func saveNewNote(name: String, text: String){
let note = Note(context: viewContext)
note.id = UUID()
note.name = name
note.text = text
do{
try viewContext.save()
} catch{
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
private func deleteSelectedNotes(){
withAnimation{
let selectedNotes = notes.filter {selectedNotesIds.contains($0.id)}
deleteNotes(notes: selectedNotes)
}
}
private func deleteNotes(notes: [Note]){
viewContext.perform{ notes.forEach(viewContext.delete)}
}
}
My Folder+CoreDataProperties:
extension Folder {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Folder> {
return NSFetchRequest<Folder>(entityName: "Folder")
}
#nonobjc class func fetchRequest(forID folderId: UUID) -> NSFetchRequest<Folder> {
let request = Folder.fetchRequest()
request.predicate = NSPredicate(format: "id = %#", folderId as CVarArg)
return request
}
#NSManaged public var folderId: UUID?
#NSManaged public var folderName: String?
#NSManaged public var notes: NSSet?
public var notesArray: [Note] {
let set = notes as? Set<Note> ?? []
return set.sorted {
$0.wrappedName > $1.wrappedName
}
}
}
Notes+CoreDataProperties:
extension Note {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Note> {
return NSFetchRequest<Note>(entityName: "Note")
}
#nonobjc class func fetchRequest(forID id: UUID) -> NSFetchRequest<Note> {
let request = Note.fetchRequest()
request.predicate = NSPredicate(format: "id = %#", id as CVarArg)
return request
}
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var text: String?
#NSManaged public var folder: Folder?
public var wrappedName: String {
name ?? "folderName"
}
}

Related

How to have a new instance of a view when a new item is created (swiftui)?

I was not sure how to word the question. I am making a simple notes app and am using a three column view. I want the user to create a new folder which is initially empty but they can add notes to it. My problem is that every folder goes to the same notes. If a new note is created in one folder, you see it in all the others.
Example (yes I know both folders are generically set to "New Folder", I'm working on trying to let the user set names):
I know the general problem is that when a new folder is created, they all go to the "NoteView". But I want a newly created folder to go to a different VERSION of the "NoteView" that is initially empty. Like you see in any Notes app, I want each folders' notes to be different, not them all referencing the same notes.
My code is below. Sidebar is where the folders are displayed. NoteView is where the corresponding notes are supposed to be displayed.
struct ContentView: View {
var body: some View {
NavigationView{
Sidebar()
Text("No folder selected")
Text("No note selected")
}
}
}
struct Sidebar: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.folderName, ascending: true)])
var folders: FetchedResults<Folder>
#State private var selectedFoldersIds: Set<Folder.ID> = []
private var selectedFolder: Folder?{
guard let selectedFolderId = selectedFoldersIds.first,
let selectedFolder = folders.filter ({$0.id == selectedFolderId}).first else{
return nil
}
return selectedFolder
}
var body: some View {
List(folders, selection: $selectedFoldersIds){folder in
NavigationLink(folder.folderName, destination:NoteView()) //each folder created navigates to NoteView() which stores all created notes. But I want each folder to have empty notes until one is added.
}
.listStyle(SidebarListStyle())
.onDeleteCommand(perform: deleteSelectedFolders)
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(action: createFolder){
Label("Create Quick Note", systemImage: "square.and.pencil")
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedFolders){
Label("Delete note", systemImage: "trash")
}
}
}
}
private func createFolder(){
createFolder(name: "New Folder")
}
private func createFolder(name: String) {
withAnimation {
let folder = Folder(context: viewContext)
folder.folderId = UUID()
folder.folderName = name
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteSelectedFolders(){
withAnimation{
let selectedFolders = folders.filter {selectedFoldersIds.contains($0.id)}
deleteFolders(folders: selectedFolders)
}
}
private func deleteFolders(folders: [Folder]){
viewContext.perform{ folders.forEach(viewContext.delete)}
}
}
struct NoteView: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var isPresented: Bool = false
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)])
var notes: FetchedResults<Note>
#State private var selectedNotesIds: Set<Note.ID> = []
private var selectedNote: Note?{
guard let selectedNoteId = selectedNotesIds.first,
let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{
return nil
}
return selectedNote
}
var body: some View {
List(notes, selection: $selectedNotesIds){ note in
NavigationLink(note.name, destination: NoteEditor(note: note ))
}
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(action: createNote){
Label("Create Quick Note", systemImage: "square.and.pencil")
}
}
ToolbarItem(placement: .primaryAction){
Button(action: deleteSelectedNotes){
Label("Delete note", systemImage: "trash")
}
}
}
}
private func createNote(){
createNote(name: "New note", text: "")
}
private func createNote(name: String, text: String) {
withAnimation {
let note = Note(context: viewContext)
note.id = UUID()
note.name = name
note.text = text
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteSelectedNotes(){
withAnimation{
let selectedNotes = notes.filter {selectedNotesIds.contains($0.id)}
deleteNotes(notes: selectedNotes)
}
}
private func deleteNotes(notes: [Note]){
viewContext.perform{ notes.forEach(viewContext.delete)}
}
}

Why does NavigationLink make my app crash when there are no errors?

I am making a simple notes app. When I press a new note I want a text editor to appear for that note. I have had trouble making this run. I finally managed to build the app without errors but it crashes so clearly I didn't solve anything. I know I must have made a logic error somewhere, but I still don't understand why the app builds but crashes when I press a note.
I think the problem has to do with my NavigationLink, more specifically the destination of NoteEditor. Here is my content view:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Note.name, ascending: true)])
var notes: FetchedResults<Note>
#State private var NotesIds: Set<Note.ID> = []
private var selectedNote: Note?{
guard let NoteId = NotesIds.first,
let selectedNote = notes.filter ({$0.id == selectedNoteId}).first else{
return nil
}
return selectedNote
}
var body: some View {
NavigationView{
List(notes, selection: $NotesIds){ note in
NavigationLink(note.name, destination: NoteEditor(note: Note())) //where I think problem is
}
if let note = selectedNote{
Text(note.text)
} else{
Text("No note selected")
.foregroundColor(.secondary)
}
}
.listStyle(SidebarListStyle())
.onDeleteCommand(perform: deleteSelectedQuickNotes)
.toolbar{
ToolbarItem(placement: .primaryAction){
Button(action: createQuickNote){
Label("Create new Note", systemImage: "square.and.pencil")
}
}
}
}
private func createNote(){
createNote(name: "New note", text: "")
}
private func createNote(name: String, text: String) {
withAnimation {
let note = Note(context: viewContext)
note.id = UUID()
note.name = name
note.text = text
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteSelectedNotes(){
let selectedNotes = notes.filter {NotesIds.contains($0.id)}
deleteNotes(notes: selectedNotes)
}
private func deleteNotes(notes: [Note]){
viewContext.perform{ notes.forEach(viewContext.delete)}
}
}
Note Editor:
struct QuickNoteEditor: View {
#Environment(\.managedObjectContext) var viewContext
#ObservedObject var note: Note
var body: some View {
TextEditor(text:$note.text)
.onReceive(note.publisher(for: \.text), perform: setName)
.onReceive(
note.publisher(for: \.text)
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
){ _ in
try? PersistenceController.shared.saveContext()
}
.navigationTitle(note.name)
}
func setName(from text: String){
let text = text.trimmingCharacters(in: .whitespacesAndNewlines)
if text.count > 0{
note.name = String(text.prefix(20))
} else {
note.name = "New Note";
}
}
}
Persistence Controller (not sure if relevant)
final class PersistenceController{
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Notes")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { description, error in
if let error = error {
fatalError("cannot load data \(error)")
}
}
}
public func saveContext(backgroundContext: NSManagedObjectContext? = nil) throws{
let context = backgroundContext ?? container.viewContext
guard context.hasChanges else { return}
try context.save()
}
}
NoteEditor(note: Note()) should be NoteEditor(note: note)
Interesting technique trying to save in onReceive it might break if the context fails to save and gets in an inconsistent state.

Get PDF-Scanner in fullscreen in a sheet in SwiftUI

I am working on a PDF-Scanner and want to realize it with SwiftUI. I know that I have to work with UIViewRepresentable to get this done.
So the user should open the Camera (to scan the document) in a Sheetview, but the camera doesn't open. If I put it in a Navigationview, it works fine.
My questions are:
Is it possible to open the camera in Sheetviews (fullscreen). Or is it possible put the CameraView in a limited frame in the SheetView to get this done.
Thanks a lot
import SwiftUI
import VisionKit
import PDFKit
import UIKit
#main
struct testetApp: App {
var body: some Scene {
WindowGroup {
ContentView(scannerModel: ScannerModel())
}
}
}
struct ContentView: View {
#State var showNextView = false
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
var body: some View {
NavigationView {
VStack {
Button {
showNextView.toggle()
} label: {
Text("via Sheet")
}.sheet(isPresented: $showNextView) {
ContentView2(scannerModel: scannerModel, showNextView: $showNextView).environment(\.managedObjectContext, vm.mangager.container.viewContext)
}.padding()
NavigationLink("via Navigationlink", destination: ContentView3(scannerModel: scannerModel))
}
.padding()
}
}
}
struct ContentView2: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
#Binding var showNextView: Bool
var body: some View {
ZStack{
NavigationView{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
}
struct ContentView3: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
var body: some View {
ZStack{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
struct ScanView: View{
#Environment(\.presentationMode) var mode
#State var pdfName = ""
#State var addDoc = true
#Binding var files : [String]
#ObservedObject var scannerModel: ScannerModel
#ObservedObject var vm: CoreDataRelationshipViewModel
var body: some View{
ZStack{
VStack{
if let error = scannerModel.errorMessage {
Text(error)
} else {
ForEach(scannerModel.imageArray, id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit).contextMenu {
Button {
let items = [image]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(ac, animated: true)
} label: {
Label("share Document", systemImage: "square.and.arrow.up")
}
Divider()
Button {
scannerModel.removeImage(image: image)
} label: {
Label("delete document", systemImage: "delete.left")
}
}
}
}
}.navigationBarItems( trailing: Button(action:{
vm.createSavedPDF(a: scannerModel.imageArray)
guard pdfName.count > 0 else{
return
}
self.mode.wrappedValue.dismiss()
saveDocument(a: scannerModel.imageArray, pdfName: pdfName)
scannerModel.imageArray.removeAll()
files = getDocumentsDirectory()
}){
Text("Save")
})
if(addDoc){
VStack{
VStack{
Button(action: {
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(scannerModel.getDocumentCameraViewController(), animated: true, completion: nil)
addDoc = false
}){
VStack {
Image(systemName: "camera").font(.title)
Text("Scan Doc").font(.title)
}
}
}.padding()
}
}
}
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.pageBreakMargins = UIEdgeInsets(top: 50, left: 30, bottom: 50, right:30)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
}
}
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}

How to past an image in TextEditor SwiftUI like oneNote/notion?

I want to paste an image in line with text like apps one note or notion do. I read somewhere that it's impossible with TextEditor and I had to use UITextView instead, so I did that but I do not know how to trigger the event PASTE to paste the picture.
Once I do that I will need to figure out how to save the whole shebang in CoreData (the text only as text, the imgs as images). But now I just need help to figure out how to paste an image into a TextEditor or UITextView. Thanks
struct itemDetail: View {
#Environment(\.managedObjectContext) var viewContext //it's like a environemtn
#Environment(\.presentationMode) var presentationMode
#State var taskItem: ToDoItem?
#State var textFieldText : String
#State var textStyle = UIFont.TextStyle.body
#State var textTop : String
#FocusState var isFocused: Bool
#State var textEditorHeight : CGFloat = 20
var body: some View {
VStack(alignment: .leading) {
ZStack(alignment: .leading) {
Text(textTop)
.font(.system(.title))
.foregroundColor(.clear)
.padding(14)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
TextEditor(text: $textTop)
.font(.system(.title))
.frame(height: max(40,textEditorHeight))
.cornerRadius(10.0)
.shadow(radius: 1.0)
}.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
///UI TEXTVIEW
TextView(text: $textFieldText, textStyle: $textStyle)
.padding(.horizontal)
Button {
//save to context
taskItem!.detail = textFieldText
taskItem!.desc = textTop
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
presentationMode.wrappedValue.dismiss()
} label: {
ZStack {
Rectangle().foregroundColor(.blue).frame(height: 40).cornerRadius(10)
Text("Save").font(.subheadline).foregroundColor(.white)
}
}
Spacer()
}
}
}
//override func paste(_ sender: Any?) {
// let textAttachment = NSTextAttachment()
// textAttachment.image = UIPasteboard.general.image
// textFieldText = NSAttributedString(attachment: textAttachment)
//}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
//textview wrpaped uikit
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}

Tabview not switching tabs properly

I have a tabview (page style) that I am using to create an automatic slideshow. Each slide is not assigned to the currentIndex for some odd reason. Neither does the timer work or manually switching tabs with the default dot controls given with tabview.
If i set the tag to selectedIndex the tabview will default to the first slide.
No errors to the console.
Any help is greatly appreciated!
struct HomeView: View {
#EnvironmentObject private var hvm: HomeViewModel
#State private var selectedFilter: String = "Popular"
#State private var selectedMedia: MediaModelResult? = nil
#State private var showDetailView: Bool = false
#State private var currentIndex: Int = 0
#State var isFilterSelected: Bool = true
#Namespace var animation
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView {
ZStack {
Color.theme.background.ignoresSafeArea()
VStack {
header
VStack(alignment: .leading, spacing: 15) {
Text("Upcoming Movies")
.foregroundColor(Color.theme.secondaryAccent)
.font(.subheadline)
.padding(.leading, 15)
TabView(selection: $currentIndex) {
ForEach(hvm.upcomingFilms) { film in
MediaImageView(mediaPath: film, poster: false)
.scaledToFill()
.tag(film)
}
}
.tabViewStyle(PageTabViewStyle())
.clipShape(RoundedRectangle(cornerRadius: 5))
.padding(.horizontal, 15)
.frame(width: UIScreen.main.bounds.width, height: 180)
.onReceive(timer, perform: { _ in
withAnimation(.default) {
currentIndex = currentIndex < hvm.upcomingFilms.count ? currentIndex + 1 : 0
}
})
}
scrollViewContent
}
}
.preferredColorScheme(.dark)
.navigationBarHidden(true)
}
}
}
struct MediaImageView: View {
#StateObject var mivm: MediaImageViewModel
var poster: Bool
init(mediaPath: MediaModelResult, poster: Bool) {
self.poster = poster
_mivm = StateObject(wrappedValue: MediaImageViewModel(mediaPath: mediaPath))
}
var body: some View {
if poster {
ZStack {
if let image = mivm.poster {
Image(uiImage: image)
.resizable()
.scaledToFit()
} else if mivm.isLoading {
ProgressView()
}
}
} else {
ZStack {
if let image = mivm.backdrop {
Image(uiImage: image)
.resizable()
} else if mivm.isLoading {
ProgressView()
}
}
}
}
}
class HomeViewModel: ObservableObject {
#Published var tabBarImageNames = ["house", "rectangle.stack", "clock.arrow.circlepath", "magnifyingglass"]
#Published var filterTitles = ["Popular Now", "Top Rated", "New"]
#Published var popularFilms: [MediaModelResult] = []
#Published var topRatedFilms: [MediaModelResult] = []
#Published var upcomingFilms: [MediaModelResult] = []
#Published var popularTV: [MediaModelResult] = []
#Published var topRatedTV: [MediaModelResult] = []
private let dataService = MediaDataService()
private var cancellables = Set<AnyCancellable>()
init() {
addSubscribers()
}
func addSubscribers() {
dataService.$popularFilms
.sink { [weak self] (returnedFilms) in
self?.popularFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$topRatedFilms
.sink { [weak self] (returnedFilms) in
self?.topRatedFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$upcomingFilms
.sink { [weak self] (returnedFilms) in
self?.upcomingFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$popularTV
.sink { [weak self] (returnedFilms) in
self?.popularTV = returnedFilms
}
.store(in: &cancellables)
dataService.$topRatedTV
.sink { [weak self] (returnedFilms) in
self?.topRatedTV = returnedFilms
}
.store(in: &cancellables)
}
}
Solution:
TabView(selection: $currentIndex) {
ForEach(0..<hvm.upcomingFilms.count) { film in
MediaImageView(mediaPath: hvm.upcomingFilms[film], poster: false)
.scaledToFill()
}
}