Get a specific id in a modal - swift

I'm still learning on the job and my question may seem stupid.
I've got a list of movies and on the tap I want to show card of the selected movie.
So I've got my ResultsView
var results:[DiscoverResult]
#State private var resultsCount:Int = 0
#State private var isPresented:Bool = false
#EnvironmentObject private var genres:Genres
var body: some View {
ScrollView {
ForEach (results){ result in
Button(action: {
isPresented.toggle()
}, label: {
ZStack {
ZStack {
KFImage(URL (string: baseUrlForThumb + result.posterPath)).resizable().scaledToFill()
.frame( height: 150)
.mask(Rectangle().frame( height: 150))
Rectangle().foregroundColor(.clear) // Making rectangle transparent
.background(LinearGradient(gradient: Gradient(colors: [.clear, .clear, .black]), startPoint: .top, endPoint: .bottom))
}.frame( height: 150)
// Titre du film
VStack(alignment: .center) {
Spacer()
Text(result.title)
.fontWeight(.bold)
.foregroundColor(.white)
.multilineTextAlignment(.center)
// Genres du film
Text(genres.generateGenresList(genreIDS: result.genreIDS)).font(.caption).foregroundColor(.white).multilineTextAlignment(.center)
} .padding()
}.padding(.horizontal)
})
.sheet(isPresented: $isPresented, content: {
MovieView(isPresented: $isPresented, movieId: result.id)
})
.navigationTitle(result.title)
}
}
}
}
And my MovieView
import SwiftUI
struct MovieView: View {
#Binding var isPresented: Bool
var movieId:Int
var body: some View {
VStack {
Text(String(movieId))
.padding()
Button("Fermer") {
isPresented = false
}
}
}
}
But the movie card still the same even list element selected.
I think that the 'result.id' is overwrite at every loop but i don't know how to fix it.
Sorry for my english mistakes.
thank for your purpose.

Instead of using isPresented for .sheet you can use .sheet(item:, content:) and pass the whole result object
.sheet(item: $selecteditem( { result in
MovieView(item: result)
}
To make this work you need a new property (you can remove isPresented)
#State private var selectedItem: DiscoverResult?
and you need update your MovieView struct
struct MovieView: View {
let result: DiscoverResult
var body: some View {
//...
}
}
or pass only the movie id to your MovieView if you prefer that.

Related

How to update #ObservedObject object while adding items to the class

I have 2 sections (ingredients selection and ingredient added). In the ingredient selection, i get all items from a json file. The goal is whenever i chose an ingredient from the ingredient selection, the item should be added to the ingredient added section. However the actual result is that I am able to add all ingredients in AddedIngredients. However my #ObservedObject var ingredientAdded :AddedIngredients indicates no items, so nothing in the section.
What am I missing in my logic?
Is it because the #Published var ingredients = [Ingredient]() is from a struct which creates a new struct every time ? My understanding is that since AddedIngredients is a class, it should reference the same date unless I override it.
This my model:
import Foundation
struct Ingredient: Codable, Identifiable {
let id: String
let price: String
let ajoute: Bool
}
class AddedIngredients: ObservableObject {
#Published var ingredients = [Ingredient]()
}
The ingredient section that displays all ingredients:
struct SectionIngredientsSelection: View {
let ingredients: [Ingredient]
#StateObject var ajoute = AddedIngredients()
var body: some View {
Section(header: VStack {
Text("Ajouter vos ingredients")
}) {
ForEach(ingredients){ingredient in
HStack{
HStack{
Image("mais")
.resizable()
.frame(width: 20, height: 20)
Text(ingredient.id)
}
Spacer()
HStack{
Text(ingredient.price )
Button(action: {
ajoute.ingredients.append(ingredient)
print(ajoute.ingredients.count)
}){
Image(systemName: "plus")
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.3257463574, blue: 0, alpha: 1)))
}
}
}
}
.listRowBackground(Color.white)
.listRowSeparator(.hidden)
}
}
}
Whenever I add an ingredient from the previous section, it should appear on this section.
struct SectionIngredientsSelected: View {
#ObservedObject var ingredientAdded :AddedIngredients
var body: some View {
Section(header: VStack {
HStack{
Text("Vos ingredients")
.textCase(nil)
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.black)
Button(action: {print(ingredientAdded.ingredients.count)}, label: {Text("Add")})
}
}) {
ForEach(ingredientAdded.ingredients){ingredient in
HStack{
HStack{
Image("mais")
.resizable()
.frame(width: 20, height: 20)
Text(ingredient.id)
}
Spacer()
HStack{
Text(ingredient.price )
Button(action: {
}){
Image(systemName: "xmark.circle")
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.3257463574, blue: 0, alpha: 1)))
}
}
}
}
.listRowBackground(Color.white)
.listRowSeparator(.hidden)
}
}
}
Main View
struct CreationView: View {
let ingredients: [Ingredient] = Bundle.main.decode("Ingredients.json")
var addedIngre: AddedIngredients
var body: some View {
ScrollView {
VStack{
VStack(alignment: .leading) {
Image("creation")
.resizable()
.scaledToFit()
Text("Slectionnez vos ingredients preferes").fontWeight(.light)
Divider()
.padding(.bottom)
}
}
}
List {
SectionIngredientsSelected(ingredientAdded: addedIngre)
SectionIngredientsSelection(ingredients: ingredients)
}
}
}
You are creating two different instances of AddedIngredients. These do not synchronize their content with each other. The simpelest solution would be to pull the AddedIngredients up into CreationView and pass it down to the sub views.
So change your CreationView to:
struct CreationView: View {
let ingredients: [Ingredient] = Bundle.main.decode("Ingredients.json")
#StateObject private var addedIngre: AddedIngredients = AddedIngredients()
and:
SectionIngredientsSelected(ingredientAdded: addedIngre)
SectionIngredientsSelection(ingredients: ingredients, ajoute: addedIngre)
and SectionIngredientsSelection to:
struct SectionIngredientsSelection: View {
let ingredients: [Ingredient]
#ObservedObject var ajoute: AddedIngredients
Remarks:
It is not perfectly clear for me how CreationView aquires addedIngre. From your code it seems it gets somehow injected. If this is the case and you need it upward the view hierachy, change the #StateObject to #ObservedObject. But make sure it is initialized with #StateObject.

Having trouble with showing another view

I'm currently trying to input another listview in my contentView file to test if it'll show, but for some reason it isn't showing the list. I'm having a bit of trouble understanding why this is happening as I am not receiving any error message.
This is the code for the list file
import SwiftUI
extension Image{
func anotherImgModifier() -> some View{
self
.resizable()
.scaledToFill()
.frame( width: 75, height: 75)
.cornerRadius(9)
}
}
struct PokeListView: View {
#State var imgURL: String = ""
#EnvironmentObject var pokeWebService: PokeWebService
//functions
// func loadImage() async -> [Image]{
// for
// }
var body: some View {
NavigationView {
List( pokeWebService.pokeList?.results ?? [], id: \.id){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url, counter: 4, name: pokemon.name)) {
AsyncImage(url:URL(string: "https://play.pokemonshowdown.com/sprites/bw/\(pokemon.name).png")){ image in
image.anotherImgModifier()
}
placeholder: {
Image(systemName: "photo.circle.fill").iconModifer()
}.padding(40)
Text(pokemon.name.uppercased()).font(.system(size: 15, weight: .heavy, design: .rounded))
.foregroundColor(.gray)
.task{
do{
try await pokeWebService.getPokemonFromPokemonList(from: pokemon.url)
} catch{
print("---> task error: \(error)")
}
}
}
}
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(PokeWebService())
}
}
This is the code for the ContentView where I was trying to input the list file.
import SwiftUI
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
#StateObject var pokeWebService = PokeWebService()
let gbImg = Image("pokeball").resizable()
#State private var gridLayout: [GridItem] = [ GridItem(.flexible()), GridItem(.flexible())]
#State private var gridColumn: Int = 2
#State var selection: Int? = nil
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false, content: {
VStack(alignment: .center, spacing: 15, content: {
Spacer()
NewsCapsule()
//GRID
//BERRIES, POKEMON, GAMES
GroupBox(label: Label{
Text("PokéStuff")
} icon: {
Image("pokeball").resizable().scaledToFit().frame(width: 30, height: 30, alignment: .leading)
}
, content: {
PokeListView()
}).padding(.horizontal, 20).foregroundColor(.red)
})//:VSTACK
})//:SCROLLVIEW
.navigationBarTitle("Pokemon",displayMode: .large)
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Image(systemName: "moon.circle")
.resizable()
.scaledToFit()
.font(.title2)
.foregroundColor(.red)
})
})
}//:NAVIGATIONBAR
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NewsWebService())
.environmentObject(PokeWebService())
}
}
How would I get to fix this?
EDIT-1:
with further tests, this is what worked for me:
in ContentView, add .frame(height: 666) to the VStack {...}.
This is the reason why you do not see anything. You need a frame height.
Also in ContentView, add .environmentObject(pokeWebService) to the NavigationView,
and just use PokeListView(). This is to pass the pokeWebService
to that view. After that, all works for me. You may want to experiment
with different frame sizes and such likes. You should also remove the NavigationView from your PokeListView, there is no need for it.

Why My second view cannot jump back to the root view properly

My App currently has two pages, first page has a circle plus button which could lead us to a second page. Basically, I have a save button which after clicking it, we could get back to the rood page. I followed this link for going back to root view. I tried the most up voted code, his code works perfectly. I reduced his code to two scene (basically the same scenario as mine), which also works perfectly. But then I don't know why my own code, pasted below, doesn't work. Basically my way of handling going back to root view is the same as the one in the link.
//
// ContentView.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/22/20.
//
import SwiftUI
import UIKit
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif
struct ContentView: View {
#EnvironmentObject private var fridge : Fridge
private var dbStartWith=0;
#State var pushed: Bool = false
#State private var selection = 1;
#State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}.environmentObject(fridge)
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
#Binding var pushed : Bool
#EnvironmentObject private var fridgeView : Fridge
#State private var name = ""
#State private var count : Int = 1
#State private var category : String = "肉类";
#State var showCategory = false
#State var showCount = false
var someNumberProxy: Binding<String> {
Binding<String>(
get: { String(format: "%d", Int(self.count)) },
set: {
if let value = NumberFormatter().number(from: $0) {
self.count = value.intValue;
}
}
)
}
var body: some View{
ZStack{
NavigationView{
VStack{
Button (action: {
self.pushed = false ;
//let tempFood=Food(id: fridgeView.index!,name: name, count: count, category: category);
//fridgeView.addFood(food: tempFood);
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you read my code carefully, there are some variables are missing referencing. That's because I pasted part of the code that relates to my issue.
Food Class
//
// Food.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/23/20.
//
import Foundation
class Food: Identifiable {
init(id:Int, name: String, count: Int, category: String){
self.id=id;
self.name=name;
self.count=count;
self.category=category;
}
var id: Int
var name: String
var count: Int
var category: String
}
Fridge class
//
// Fridge.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/27/20.
//
import Foundation
class Fridge: ObservableObject{
init(){
db=DBhelper();
let result = setIndex(database: db!);
self.index = result.1;
self.container=result.0;
}
var db:DBhelper?
var index : Int?
#Published var container : [Food]?;
func setIndex(database: DBhelper) -> ([Food],Int){
let foodList : [Food] = database.read();
var index=0;
for food in foodList{
index = max(food.id,index);
}
return (foodList,(index+1));
}
func updateindex(index: inout Int){
index=index+1;
}
func testExist(){
if let data = db {
print("hi")
}
else{
print("doesnt exist")
}
}
func addFood(food:Food){
self.db!.insert(id: self.index!, name: food.name, count:food.count, category: food.category);
self.container!.append(food);
}
}
Because you implemented a new NaviagtionView in AddFoodView. Simply remove this and it should work. Look at the link you provided. There is no NavigationView in the child.
Correct me if Im wrong but the core code parts here that produce this issue are as follows:
Here you start:
struct ContentView: View {
#State var pushed: Bool = false
// Deleted other vars
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
// Here you navigate to the child view
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
Here you land and want to go back to root:
struct AddFoodView: View{
#Binding var pushed : Bool
// Deleted the other vars for better view
var body: some View{
ZStack{
NavigationView{ // <-- remove this
VStack{
Button (action: {
// here you'd like to go back
self.pushed = false;
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
For the future:
I have the feeling you might have troubles with the navigation in general.
Actually it is really simple:
You implement one NavigationView at the "root" / start of your navigation.
From there on you only use NavigationLinks to go further down to child pages. No NavigationView needed anymore.

is it possible get List array to load horizontally in swiftUI?

Do I need to dump using List and just load content into a Scrollview/HStack or is there a horizontal equivalent to stack? I would like to avoid having to set it up differently, but am willing todo so if there is no alternative... it just means recoding multiple other views.
current code for perspective:
import SwiftUI
import Combine
struct VideoList: View {
#Environment(\.presentationMode) private var presentationMode
#ObservedObject private(set) var viewModel: ViewModel
#State private var isRefreshing = false
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Home") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
}
}
}
var body: some View {
NavigationView {
List(viewModel.videos.sorted { $0.id > $1.id}, id: \.id) { video in
NavigationLink(
destination: VideoDetails(viewModel: VideoDetails.ViewModel(video: video))) {
VideoRow(video: video)
}
}
.onPullToRefresh(isRefreshing: $isRefreshing, perform: {
self.viewModel.fetchVideos()
})
.onReceive(viewModel.$videos, perform: { _ in
self.isRefreshing = false
})
}
.onAppear(perform: viewModel.fetchVideos)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
In general, List is List and it by design is vertical-only. For all horizontal case we should use ScrollView+HStack or ScrollView+LazyHStack (SwiftUI 2.0).
Anyway here is a simple demo of possible way that can be applicable in some particular cases. Prepared & tested with Xcode 12 / iOS 14.
Note: all tuning and alignments fixes are out of scope - only possibility demo.
struct TestHorizontalList: View {
let data = Array(1...20)
var body: some View {
GeometryReader { gp in
List {
ForEach(data, id: \.self) {
RowDataView(item: $0)
.rotationEffect(.init(degrees: 90)) // << rotate content back
}
}
.frame(height: gp.size.width) // initial fit in screen
.rotationEffect(.init(degrees: -90)) // << rotate List
}
}
}
struct RowDataView: View {
let item: Int
var body: some View {
RoundedRectangle(cornerRadius: 25.0).fill(Color.blue)
.frame(width: 80, height: 80)
.overlay(
Text("\(item)")
)
}
}

SwiftUI 2 Observable objects in one view which depend on each other

Thanks for all the support I have received, I trying to build an macos app that tags pdfs for machine learning purposes. I have followed Stanford SwiftUI course, and I want to create main view for my app that contains the document and to type a regex string to find in the document. The deal is I need to create a document chooser, to add documents to be analized, but I don't know how to deal with 2 view models in the same view. In fact one of those view models depend on the other one. The solution I found (not a solution a messy workaround) is to initialize the document manager as a separate view and use it as a navigation view, with a navigation link, but the look is horrible. I'll paste the code and explain it better.
This is the stores view
struct PDFTaggerDocumentStoreView: View {
#EnvironmentObject var store:PDFTaggerDocumentStore
var body: some View {
NavigationView {
List {
Spacer()
Text("Document Store").fontWeight(.heavy)
Button(action: {self.store.addDocument()}, label: {Text("Add document")})
Divider()
ForEach(store.documents){ doc in
NavigationLink(destination: PDFTaggerMainView(pdfTaggerDocument: doc)) {
Text(self.store.name(for: doc))
}
}
.onDelete { indexSet in
indexSet.map{self.store.documents[$0]}.forEach { (document) in
self.store.removeDocuments(document)
}
}
}
}
}
}
The main view.
struct PDFTaggerDocumentView: View {
#ObservedObject var document:PDFTaggerDocument
#State private var expression = ""
#State private var regexField = ""
#State private var showExpressionEditor = false
var body: some View {
VStack {
ZStack {
RoundedRectangle(cornerRadius: 15, style: .continuous)
.stroke(Color.white, lineWidth: 0.5)
.frame(width: 600)
.padding()
VStack {
Text("Try expression ")
HStack {
TextField("Type regex", text: $document.regexString)
.frame(width: 200)
Image(nsImage: NSImage(named: "icons8-save-80")!)
.scaleEffect(0.3)
.onTapGesture {
self.showExpressionEditor = true
print(self.document.regexString)
print(self.regexField)
}
.popover(isPresented: $showExpressionEditor) {
ExpressionEditor().environmentObject(self.document)
.frame(width: 200, height: 300)
}
}
Picker(selection: $expression, label: EmptyView()) {
ForEach(document.expressionNames.sorted(by: >), id:\.key) { key, value in
Text(key)
}
}
Button(action: self.addToDocument, label: {Text("Add to document")})
.padding()
.frame(width: 200)
}
.frame(width:600)
.padding()
}
.padding()
Rectangle()
.foregroundColor(Color.white).overlay(OptionalPDFView(pdfDocument: document.backgroundPDF))
.frame(width:600, height:500)
.onDrop(of: ["public.file-url"], isTargeted: nil) { (providers, location) -> Bool in
let result = providers.first?.hasItemConformingToTypeIdentifier("public.file-url")
providers.first?.loadDataRepresentation(forTypeIdentifier: "public.file-url") { data, error in
if let safeData = data {
let newURL = URL(dataRepresentation: safeData, relativeTo: nil)
DispatchQueue.main.async {
self.document.backgroundURL = newURL
}
}
}
return result!
}
}
}
I'd like to be able to initialize both views models in the same view, and make the document view, be dependent on the document chooser model.
Is there a way I can do it?
Thanks a lot for your time.