SwiftUI image in view not updating after fetching data from firebase - firebase

After I add a post with an image and I go back to the main feed view, I can see the post info(username, upload date etc.); but instead of an image, there is only a gap.
After I go to another tab and come back to Main Feed View, I can see the image then. Why I can't fetch the image and update the UI immediately?
this is the FeedCellView
import SwiftUI
import Kingfisher
struct FeedCell: View {
#ObservedObject var viewModel: FeedCellViewModel
#EnvironmentObject var auth: AuthViewModel
#Environment(\.presentationMode) var presentationMode
var didLike: Bool { return viewModel.post.didLike ?? false }
#State var showActionSheet = false
init(viewModel: FeedCellViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack(alignment: .leading) {
// user info
HStack {
if let user = viewModel.post.user{
NavigationLink(destination: ProfileView(user: user)){
KFImage(URL(string: viewModel.post.ownerImageUrl))
.resizable()
.scaledToFill()
.frame(width: 36, height: 36)
.clipped()
.cornerRadius(18)
Text(viewModel.post.ownerUsername)
.font(.system(size: 14, weight: .semibold))
}
}
}
.padding(.bottom, 8)
.padding(.leading, 8)
.padding(.trailing, 12)
// post image
KFImage(URL(string: viewModel.post.imageUrl))
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width - 4, alignment: .center)
.frame(maxHeight: 440)
.clipped()
.padding(.leading, 2)
// action buttons
HStack(spacing: 16) {
Button(action: {
didLike ? viewModel.unlike() : viewModel.like()
}, label: {
Image(systemName: didLike ? "heart.fill" : "heart")
.resizable()
.scaledToFill()
.foregroundColor(didLike ? .red : .black)
.frame(width: 20, height: 20)
.font(.system(size: 20))
.padding(4)
})
NavigationLink(destination: CommentsView(post: viewModel.post)) {
Image(systemName: "bubble.right")
.resizable()
.scaledToFill()
.frame(width: 20, height: 20)
.font(.system(size: 20))
.padding(4)
}
Button(action: {}, label: {
Image(systemName: "paperplane")
.resizable()
.scaledToFill()
.frame(width: 20, height: 20)
.font(.system(size: 20))
.padding(4)
})
}
.padding(.leading, 4)
.foregroundColor(.black)
// caption
Text(viewModel.likeString)
.font(.system(size: 14, weight: .semibold))
.padding(.leading, 8)
.padding(.bottom, 0.5)
//
HStack {
Text(viewModel.post.ownerUsername).font(.system(size: 14, weight: .semibold)) +
Text(" \(viewModel.post.caption)")
.font(.system(size: 14))
}.padding(.horizontal, 8)
Text(viewModel.timestampString)
.font(.system(size: 14))
.foregroundColor(.gray)
.padding(.leading, 8)
.padding(.top, -2)
}
}
func getActionSheet() -> ActionSheet{
return ActionSheet(title: Text("What would you like to do?"), message: nil, buttons: [
.destructive(Text("Delete"), action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
viewModel.deletePost()
}
}),
.default(Text("Learn more..."), action: {
print("LEARN MORE PRESSED")
}),
.cancel()
])
}
}
struct FeedCell_Previews: PreviewProvider {
static var previews: some View {
FeedCell(viewModel: FeedCellViewModel(post: postExample))
}
}
this is the viewModel
import SwiftUI
import Firebase
class FeedCellViewModel: ObservableObject{
#Published var post: Post
var likeString: String{
let label = post.likes == 1 ? "like" : "likes"
return "\(post.likes) \(label)"
}
var timestampString: String{
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.second, .minute, .hour, .day, .weekOfMonth]
formatter.maximumUnitCount = 1
formatter.unitsStyle = .abbreviated
return formatter.string(from: post.timestamp.dateValue(), to: Date()) ?? ""
}
init(post: Post){
self.post = post
checkIfUserLikedPost()
fetchPostUser()
}
func like(){
guard let uid = AuthViewModel.shared.userSession?.uid else {return}
guard let postId = post.id else {return}
Firestore.firestore().collection("posts").document(post.id ?? "").collection("post-likes").document(uid).setData([:]){_ in
Firestore.firestore().collection("users").document(uid).collection("user-likes").document(postId).setData([:]){ _ in
Firestore.firestore().collection("posts").document(postId).updateData(["likes": self.post.likes + 1])
NotificationsViewModel.uploadNotification(toUid: self.post.ownerUid, type: .like, post: self.post)
self.post.didLike = true
self.post.likes += 1
}
}
}
func unlike(){
guard post.likes > 0 else {return}
guard let uid = AuthViewModel.shared.userSession?.uid else {return}
guard let postId = post.id else {return}
Firestore.firestore().collection("posts").document(post.id ?? "").collection("post-likes").document(uid).delete{_ in
Firestore.firestore().collection("users").document(uid).collection("user-likes").document(postId).delete{ _ in
Firestore.firestore().collection("posts").document(postId).updateData(["likes": self.post.likes - 1])
self.post.didLike = false
self.post.likes -= 1
}
}
}
func checkIfUserLikedPost(){
guard let uid = AuthViewModel.shared.userSession?.uid else {return}
guard let postId = post.id else {return}
Firestore.firestore().collection("users").document(uid).collection("user-likes").document(postId).getDocument{ snapshot, _ in
guard let didLike = snapshot?.exists else {return}
self.post.didLike = didLike
}
}
func deletePost(){
guard let postId = post.id else {return}
Firestore.firestore().collection("posts").document(postId).delete()
}
func fetchPostUser(){
Firestore.firestore().collection("users").document(post.ownerUid).getDocument { snapshot, _ in
self.post.user = try? snapshot?.data(as: User.self)
}
}
}

Related

Display data from firestore

I am trying to display the data I get back from the firestore in my swiftUI app but am having some trouble.
Here is where I get the data
import Foundation
import Firebase
class RecipesViewModel: ObservableObject {
#Published var userRecipes = [RecipesData]()
func getData() {
// Get a reference to the database
let db = Firestore.firestore()
// Read the documents at a specific path
db.collection("Recipes").getDocuments { snapshot, error in
// Check for errors
if error == nil {
// No errors
if let snapshot = snapshot {
// Update the list property in the main thread
DispatchQueue.main.async {
self.userRecipes = snapshot.documents.map { d in
return RecipesData(id: d["id"] as? String ?? "", name: d["name"] as? String ?? "", cuisine: d["cuisine"] as? String ?? "", difficulty: d["difficulty"] as? String ?? "", dishType: d["dishType"] as? String ?? "", prepMins: d["prepMins"] as? Int ?? 0, prepHours: d["prephours"] as? Int ?? 0, cookMins: d["cookMins"] as? Int ?? 0, cookHours: d["cookHours"] as? Int ?? 0, restMins: d["restMins"] as? Int ?? 0, restHours: d["restHours"] as? Int ?? 0, likes: d["lkies"] as? Int ?? 0)
}
}
}
}
else {
}
}
}
}
and here is where the data gets stored
import SwiftUI
import FirebaseFirestoreSwift
struct RecipesData: Identifiable{
var id: String
var name: String
var cuisine: String
var difficulty: String
var dishType: String
var prepMins: Int
var prepHours: Int
var cookMins: Int
var cookHours: Int
var restMins: Int
var restHours: Int
var likes: Int
}
I can get the Data and if I choose to display the name of each recipe in a list I can do that.
But what I want to do is to have my data too like this when being displayed
import SwiftUI
struct AllRecipesView: View {
#ObservedObject var model = RecipesViewModel()
private var gridCollum = [GridItem(.flexible(),spacing: 0), GridItem(.flexible(),spacing: 0)]
var body: some View {
VStack{
LazyVGrid(columns: gridCollum, spacing: 0){
ForEach(model.userRecipes) {item in
overlayView()
}
}
}
}
init(){
model.getData()
}
}
struct AllRecipesView_Previews: PreviewProvider {
static var previews: some View {
AllRecipesView()
}
}
struct overlayView:View{
#ObservedObject var model = RecipesViewModel()
var body: some View{
ForEach(model.userRecipes) {item in
VStack{
VStack{
Spacer()
HStack{
HStack{
Image(systemName: "star")
.foregroundColor(.white)
.font(.system(size: 20))
Text(item.likes)
.foregroundColor(.white)
.font(.system(size: 15))
}
.padding(.trailing)
Text(item.prepMins)
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.horizontal)
}
.padding(.bottom)
}
.frame(width:180,height:130)
.background(Color.red)
.cornerRadius(8)
.shadow(color: .black, radius: 3, x: 2, y: 2)
.padding(.bottom)
Text("Salmon and Rice")
Text("Some User")
}
}
}
init(){
model.getData()
}
}
But I keep getting error saying No exact matches in call to initializer on the lines
Text(item.likes)
and
Text(prepMins)
How can I fix my errors please
try something like this example code, using #StateObject var model = RecipesViewModel() and passing it using #EnvironmentObject.
To fix the errors you get, remenber Text() needs a string, such as, Text(String(item.likes)) or Text("\(item.likes)").
struct AllRecipesView: View {
#StateObject var model = RecipesViewModel() // <-- here
private var gridCollum = [GridItem(.flexible(),spacing: 0), GridItem(.flexible(),spacing: 0)]
var body: some View {
VStack{
LazyVGrid(columns: gridCollum, spacing: 0){
ForEach(model.userRecipes) {item in
OverlayView()
}
}
}
.environmentObject(model) // <-- here
.onAppear {
model.getData() // <-- here
}
}
}
struct OverlayView:View{
#EnvironmentObject var model: RecipesViewModel // <-- here
var body: some View{
ForEach(model.userRecipes) {item in
VStack{
VStack{
Spacer()
HStack{
HStack{
Image(systemName: "star")
.foregroundColor(.white)
.font(.system(size: 20))
Text(String(item.likes)) // <-- here
.foregroundColor(.white)
.font(.system(size: 15))
}
.padding(.trailing)
Text(String(item.prepMins)) // <-- here
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.horizontal)
}
.padding(.bottom)
}
.frame(width:180,height:130)
.background(Color.red)
.cornerRadius(8)
.shadow(color: .black, radius: 3, x: 2, y: 2)
.padding(.bottom)
Text("Salmon and Rice")
Text("Some User")
}
}
}
}

Firestore doesn't save data

I am trying to make a simple app that lets users create recipes and then upload them to the firestore
I am using an environment object to store the data until I upload it to the firestore
My problem is that when I check the firestore to see if my data has uploaded, the data there is empty
Here is my recipe class:
import Foundation
class recipe: ObservableObject{
#Published var name: String = ""
#Published var prepMins: Int = 0
#Published var prepHours: Int = 0
#Published var cookMins: Int = 0
#Published var cookHours: Int = 0
#Published var restMins: Int = 0
#Published var restHours: Int = 0
#Published var dishType: String = ""
#Published var cuisine: String = ""
#Published var difficulty: String = ""
#Published var instructions: [String] = []
#Published var ingredients: [String] = []
}
Here is a sample upload view:
import SwiftUI
struct UploadView: View {
#State private var dishType = "Starter"
#State private var cuisine = "European"
#State private var difficulty = "Easy"
#State var name: String = ""
#State private var isPresented = false
#State private var isPresented1 = false
#State var prepHours: Int = 0
#State var prepMins: Int = 0
#State var cookHours: Int = 0
#State var cookMins: Int = 0
#State var restHours: Int = 0
#State var restMins: Int = 0
#StateObject var recipe1 = recipe()
#EnvironmentObject var viewRouter: ViewRouter
var options = ["Easy", "Medium", "Difficult"]
var body: some View {
ScrollView{
VStack{
HStack{
Spacer()
Text("Create a Recipe")
.font(.system(size: 30))
.fontWeight(.bold)
Spacer()
}
.padding(.top)
}
Group{
HStack{
Text("Name")
.padding()
.font(.system(size: 25, weight: .medium))
Spacer()
}
HStack{
TextField("Enter a name", text: $name)
.padding()
.zIndex(1)
Spacer()
}
}
Group{
HStack{
Text("Choose a dish type")
.padding()
Spacer()
}
HStack{
Button(dishType) {
isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented) {
SelectionView()
}
Spacer()
}
.padding(.horizontal)
HStack{
Text("Choose a cuisine")
.padding()
Spacer()
}
HStack{
Button(cuisine) {
isPresented1.toggle()
}
.fullScreenCover(isPresented: $isPresented1) {
SelectionView1()
}
Spacer()
}
.padding(.horizontal)
}
HStack{
Text("Choose a difficulty")
.padding()
Spacer()
}
Picker("Diffuculty", selection: $difficulty) {
ForEach(options, id: \.self) {
Text($0)
}
}
.pickerStyle(.segmented)
.padding(.horizontal)
.padding(.bottom)
Group{
VStack{
HStack{
Text("Prep Time")
.fontWeight(.medium)
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
HStack{
Text("How long does it take to make the dish")
.fontWeight(.light)
.padding(.horizontal)
Spacer()
}
}
HStack{
HStack{
VStack {
Picker("", selection: $prepHours){
ForEach(0..<12, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
}
VStack {
Picker("", selection: $prepMins){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
}
}
}
}
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
VStack{
VStack{
HStack{
Text("Cook Time")
.fontWeight(.medium)
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
HStack{
Text("How long does it take to cook the dish")
.fontWeight(.light)
.padding(.horizontal)
Spacer()
}
}
}
HStack{
HStack{
VStack {
Picker("", selection: $cookHours){
ForEach(0..<12, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
}
VStack {
Picker("", selection: $cookMins){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
.foregroundColor(.black)
}
}
}
}
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
VStack{
VStack{
HStack{
Text("Rest Time")
.fontWeight(.medium)
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
HStack{
Text("How long does the dish need to rest")
.fontWeight(.light)
.padding(.horizontal)
Spacer()
}
}
}
HStack{
HStack{
VStack {
Picker("", selection: $restHours){
ForEach(0..<12, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
}
VStack {
Picker("", selection: $restMins){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
.foregroundColor(.black)
}
}
}
}
.padding(.horizontal)
.padding(.bottom)
Spacer()
}
}
Button {
//change view router
//add data to data class
recipe1.name = name
recipe1.dishType = dishType
recipe1.cuisine = cuisine
recipe1.difficulty = difficulty
recipe1.prepMins = prepMins
recipe1.prepHours = prepHours
recipe1.cookMins = cookMins
recipe1.cookHours = cookHours
recipe1.restMins = restMins
recipe1.restHours = restHours
viewRouter.currentPage = .uploadView2
} label: {
Label("next", systemImage: "arrow.right")
}
.padding()
.frame(width: 100)
.foregroundColor(Color.white)
.background(Color.red)
.cornerRadius(8)
}
.environmentObject(recipe1)
}
}
struct UploadView_Previews: PreviewProvider {
static var previews: some View {
UploadView()
.previewDevice(PreviewDevice(rawValue: "iPhone 13"))
UploadView()
.previewDevice(PreviewDevice(rawValue: "iPhone 8"))
}
}
And here is my final view with the upload function
import SwiftUI
import Firebase
struct UploadView3: View {
#EnvironmentObject var viewRouter: ViewRouter
#StateObject var recipe1 = recipe()
var body: some View{
ScrollView{
VStack{
HStack{
Button {
print("Going Back")
viewRouter.currentPage = .uploadView2
} label: {
Image(systemName: "arrow.left")
.font(.system(size: 30))
.foregroundColor(.black)
}
.padding(.horizontal)
Spacer()
Text("Add Instructions")
.font(.system(size: 30))
.fontWeight(.bold)
Spacer()
Button {
print("Saved")
} label: {
Image(systemName: "bookmark")
.font(.system(size: 30))
.foregroundColor(.black)
}
.padding()
}
Text("Add Instructions so that people can easily follow your recipe")
.padding()
Button {
self.addData()
} label: {
Label("next", systemImage: "arrow.right")
}
}
}
.environmentObject(recipe1)
}
func addData(){
let db = Firestore.firestore()
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else{return}
db.collection("Recipes").addDocument(data: ["id" : uid, "name" : recipe1.name, "dishType" : recipe1.dishType, "cuisine" : recipe1.cuisine, "difficulty" : recipe1.difficulty,"prepMins" : recipe1.prepMins, "prepHours": recipe1.prepHours, "cookMins" : recipe1.cookMins, "cookHours": recipe1.cookHours, "restMins" : recipe1.restMins, "restHours" : recipe1.restHours, "ingredients" : recipe1.ingredients])
{ err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
struct UploadView3_Previews: PreviewProvider {
static var previews: some View {
UploadView3()
}
}
if there is a better way than using an environment object please let me know, otherwise How can I pass the data between my views?
Many Thanks for your time
you have two independent #StateObject var recipe1 = recipe(), one in UploadView and in UploadView3. So when you use func addData(){...} in UploadView3, you have a new empty recipe.
You should have only one, that is the single source of truth. Then use
#ObservedObject var recipe1: recipe or .environmentObject(recipe1) and #EnvironmentObject var recipe1: recipe to pass it around.

Slight delay/lag when fetching Firestore User data to update View

I'm currently building a simple app with a SignIn/SignUp View in order to better understand how to integrate SwiftUI with Firebase.
I've run into a bit of an issue when I try to update User Data. I noticed that after Signing Out as one User and then Signing In as a different User, there is a split second delay before the User information is updated on the FirstView after the Sign In button is tapped. I'm trying to figure out how to avoid this delay from happening.
As a side, I'm also having trouble figuring out how to smoothly navigate from my LoginView() to my FirstView(). I have my FirstView() inside of a Navigation Stack, but the transition between the views is very abrupt and devoid of the NavigationLink animation. How can I correct this issue?
Much appreciated!
Here is the relevant code...
#main
struct AWSupportLoggerApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#StateObject var viewModel = AppViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
}
}
class AppDelegate:NSObject,UIApplicationDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
}
struct ContentView: View {
#EnvironmentObject var viewModel: AppViewModel
var body: some View {
NavigationView {
if viewModel.signedIn {
FirstView()
} else {
//.onAppear method is used for keyboard management (See Misc Functions...)
SignInView()
.onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
.navigationBarHidden(true)
}
}
.onAppear {
viewModel.listen()
}
}
}
class AppViewModel: ObservableObject {
private var db = Firestore.firestore()
#Published var userInfo: User?
#Published var signedIn: Bool = false
var handle: AuthStateDidChangeListenerHandle?
let authRef = Auth.auth()
var authHandle : AuthStateDidChangeListenerHandle?
var rootInfoCollection : CollectionReference!
var userIdRef = ""
func fetchUserData(){
db.collection("Users").document("\(userIdRef)").getDocument { document, error in
// Check for error
if error == nil {
// Check that this document exists
if document != nil && document!.exists {
self.userInfo = document.map { (documentSnapshot) -> User in
let data = documentSnapshot.data()
let uid = data?["uid"] as? UUID ?? UUID()
let company = data?["company"] as? String ?? ""
let name = data?["name"] as? String ?? ""
let admin = data?["admin"] as? Bool ?? false
let photo = data?["photo"] as? String ?? ""
return User(uid: uid, company: company, name: name, admin: admin, photo: photo)
}
}
}
}
}
func listen(){
handle = authRef.addStateDidChangeListener({ auth, user in
print(user?.email ?? "No User Found")
if let user = auth.currentUser {
self.userIdRef = user.uid
self.rootInfoCollection = Firestore.firestore().collection("/Users/")
DispatchQueue.main.async {
self.fetchUserData()
}
self.signedIn = true
} else {
self.signedIn = false
}
})
}
func signIn(email: String, password: String){
authRef.signIn(withEmail: email, password: password) { result, error in
guard result != nil, error == nil else {
return
}
}
}
func signOut(){
do {
try authRef.signOut()
} catch {
print(error)
}
}
func signUp(email: String, password: String, company: String, name: String, admin: Bool, photo: String){
authRef.createUser(withEmail: email, password: password) { result, error in
guard result != nil, error == nil else {
return
}
let db = Firestore.firestore()
//Success
db.collection("Users").document("\(result!.user.uid)").setData(["company" : "\(company)", "name" : "\(name)", "admin" : admin, "photo" : "\(photo)", "uid":result!.user.uid]) { error in
if error != nil {
print(error!)
}
}
}
}
func unbind() {
if let handle = handle {
authRef.removeStateDidChangeListener(handle)
}
}
}
struct FirstView: View {
#EnvironmentObject private var appViewModel: AppViewModel
var body: some View {
VStack{
Spacer()
VStack(spacing: 50){
NavigationLink(destination: Text("Test")){
awButton(content: "Request Support", backColor: Color(#colorLiteral(red: 0, green: 0.723585546, blue: 0.9907287955, alpha: 1)))
.shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
.rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
}
NavigationLink(destination: Text("Test")){
awButton(content: "Request Quote", backColor: Color(#colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)))
.shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
.rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
}
NavigationLink(destination: Text("Test")){
awButton(content: "Ticket Status", backColor: Color(#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)))
.shadow(color: Color.primary.opacity(0.5), radius: 20, x: 0, y: 20)
.rotation3DEffect(Angle(degrees:10), axis: (x: 10.0, y: 0, z: 0))
}
}
Spacer()
}
.navigationBarBackButtonHidden(true)
.navigationTitle(appViewModel.userInfo?.company ?? "Test")
.navigationBarItems(leading: Button(action: {
appViewModel.signOut()
}) {
HStack {
Text("Sign Out")
}
},trailing: HStack{
Image(systemName: "bell")
.font(.system(size: 30))
// selectedImageArray.first!
// .resizable()
// .scaledToFit()
// .clipShape(Circle())
// .frame(width: 50, height: 50)
Text(appViewModel.userInfo?.name ?? "Tester")
.font(.system(size: 20))
})
}
}
Your navigation state depends on signedIn. In your auth listener, you do this:
DispatchQueue.main.async {
self.fetchUserData()
}
self.signedIn = true
This will set signedIn to true, which will change your navigation state and then at an indeterminate time in the future, fetchUserData will finish and update the user data (once the network call has completed).
To avoid this delay (or, really, to just avoid seeing the information not update on the screen -- the delay is an inevitability of the network call), don't set signedIn until fetchUserData completes. So, remove the line inside the listener and instead, set it after your self.userInfo = line.

How do I fetch the post id's for Wordpress posts using their Rest API with SwiftUI

I'm trying to include some blog posts from my WordPress site into my app. I've tested the API using the individual post Ids for each post and I got it to load the data in my view. However, I'm now trying to fetch an array but it doesn't seem to be fetching the IDs related to the posts to populate the view. What am I doing wrong here?
let content: MarkdownData
#State var beholdarticles: BeholdArticle?
#State private var htmlContent = ""
let articles: [BeholdArticle] = []
private func loadArticle() {
guard let url = URL(string: "https://behold.hagleyparksda.com/wp-json/wp/v2/posts") else {
return
}
URLSession.shared.dataTask(with: url) {data, response, error in
guard let data = data else { return }
if let decodedData = try? JSONDecoder().decode(BeholdArticle.self, from: data){
DispatchQueue.main.async {
self.beholdarticles = decodedData
}
}
}.resume()
}
This is the ForEach loop
var body: some View {
ScrollView (.horizontal) {
ForEach(articles) { item in
ZStack {
if beholdarticles?.thumbnail != nil {
WebImage(url: URL(string: beholdarticles!.thumbnail)!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:350, height: 450)
} else {
Image("behold_imagecard")
.resizable()
.aspectRatio(contentMode: .fill)
}
HStack {
VStack (alignment: .leading) {
Text(beholdarticles?.title.rendered ?? "Loading...")
.font(.system(size: 30, weight: .bold))
.foregroundColor(Color.white)
.frame(width: 270)
// Text(beholdarticles?.title.rendered ?? "Loading...")
// .font(.system(size: 18, weight: .regular))
// .foregroundColor(Color.white)
// .frame(width: 270)
}
Spacer()
}
VStack {
Spacer()
HStack {
Image(uiImage: #imageLiteral(resourceName: "healthicon"))
Text("Spirituality")
.font(.system(size: 23))
.foregroundColor(Color.white)
Spacer()
}
.background(VisualEffectBlurView(blurStyle: .systemUltraThinMaterial))
}.onAppear{
loadArticle()
}
} .frame(width:350, height: 450)
.shadow(color: Color(#colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)) .opacity(0.2), radius: 20, x: /*#START_MENU_TOKEN#*/0.0/*#END_MENU_TOKEN#*/, y:14)
.cornerRadius(30)
}
}
My data model
struct BeholdArticle: Decodable, Identifiable {
var id: Int
var slug: String
var link: String
var thumbnail: String
var title: BeholdArticleTitle
var content: BeholdArticleContent
enum CodingKeys: String, CodingKey {
case thumbnail = "jetpack_featured_media_url"
case slug, link, title, content
case id = "id"
}
}
struct BeholdArticleTitle: Decodable {
var rendered: String
}
struct BeholdArticleContent: Decodable {
var rendered: String
}
I'm simply trying to populate my loop with the data, but it doesnt seem to be grabbing the ID's from api call. Need some help here
I still have the image URL wrapped in a conditional statement. At current the images wont come through. How do I adjust this to the updated setup?
if beholdarticles?.thumbnail != nil {
WebImage(url: URL(string: item.thumbnail)!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:350, height: 450)
} else {
Image("behold_imagecard")
.resizable()
.aspectRatio(contentMode: .fill)
}
Here's the updated image code:
ForEach(articles) { article in
ZStack {
if article.thumbnail != nil {
WebImage(url: URL(string: article.thumbnail)!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:350, height: 450)
} else {
Image("behold_imagecard")
.resizable()
.aspectRatio(contentMode: .fill)
}
If you want to decode the JSON from that URL endpoint, you're going to need to use [BeholdArticle].self instead of BeholdArticle.self, since it's an array of data.
Also, instead of calling loadArticle on each element of the ForEach (which will never get called, since it doesn't have data in it at the beginning), call it at the top level of the view.
Here's a pared down example:
struct ContentView: View {
#State private var articles : [BeholdArticle] = []
var body: some View {
ScrollView {
VStack (alignment: .leading) {
ForEach(articles) { article in
Text(article.title.rendered)
.multilineTextAlignment(.leading)
if let thumbnailURL = URL(string: article.thumbnail) {
WebImage(url: thumbnailURL)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width:350, height: 450)
}
}
}
}
.onAppear {
loadArticles()
}
}
private func loadArticles() {
guard let url = URL(string: "https://behold.hagleyparksda.com/wp-json/wp/v2/posts") else {
return
}
URLSession.shared.dataTask(with: url) {data, response, error in
guard let data = data else { return }
if let decodedData = try? JSONDecoder().decode([BeholdArticle].self, from: data){
DispatchQueue.main.async {
self.articles = decodedData
}
}
}.resume()
}
}

SwiftUI: How to play next audio

I am making app which have audio player in it and is connected with Firebase. I don't have clue how to play next song. I know that i have to try something with firstIndex but don't really know where to find the answer so i am asking here please help.
This is code:
import SwiftUI
import MediaPlayer
import AVFoundation
import FirebaseStorage
let MainColor = Color(#colorLiteral(red: 0.2588235294, green: 0.7411764706,
blue: 0.7764705882, alpha: 1))
let SecondColor = Color(#colorLiteral(red: 0.4470588235, green: 0.7490196078,
blue: 0.2705882353, alpha: 1))
let screenW = UIScreen.main.bounds.width
let screenH = UIScreen.main.bounds.height
struct ZespolView: View {
#State private var showFirst = false
#ObservedObject var bede_zycData : Bede_zycData
var body: some View{
NavigationView{
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 10){
NavigationLink(
destination: AlbumCellBedeZyc(image: "bede_zyc_img", name: "Będę żyć, by kochać", artist: "Zespół Filadelfia", numberOfSongs: "11", dateOfRelase: "2008", bede_zycData: bede_zycData),
label: {ZespolCellView(image: "bede_zyc_img", name: "Będę żyć, by kochać", artist: "Zespół Filadelfia", numberOfSongs: "11", dateOfRelase: "2008")}).foregroundColor(.black)
}
}.frame(height: screenW/2.5 + 150)
.navigationTitle("Test view")
}
}
}
struct ZespolCellView : View {
#State var image : String
#State var name : String
#State var artist : String
#State var numberOfSongs : String
#State var dateOfRelase : String
var body: some View{
VStack(alignment: .leading){
Image(image)
.resizable()
.scaledToFit()
.cornerRadius(30)
.shadow(color: Color.black.opacity(0.6), radius: 15)
.frame(width: screenW/2.5, height: screenW/2.5)
Text(name)
.font(.system(size: 15, weight: .bold))
.padding(5)
Text(artist)
.font(.system(size: 14, weight: .thin))
.padding(.horizontal, 5)
}.frame(width: screenW/2.5, height: screenW/2.5 + 150)
.padding(.horizontal)
}
}
struct AlbumCellBedeZyc : View {
#State var image : String
#State var name : String
#State var artist : String
#State var numberOfSongs : String
#State var dateOfRelase : String
#State private var isPresented = false
#ObservedObject var bede_zycData : Bede_zycData
#State private var currentSong : Bede_zycInfo?
#Environment(\.presentationMode) var mode1 : Binding<PresentationMode>
var body: some View{
VStack{
HStack{
Image(image)
.resizable()
.scaledToFit()
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.6), radius: 15)
.frame(width: screenW/2.4, height: screenW/2.4)
Spacer(minLength: 0)
VStack(alignment: .leading, spacing: 5){
HStack{
Text("Album")
.font(.system(size: 15, weight: .thin))
Circle()
.frame(width: 5, height: 5)
Text(numberOfSongs)
.font(.system(size: 15, weight: .thin))
Circle()
.frame(width: 5, height: 5)
Text(dateOfRelase)
.font(.system(size: 15, weight: .thin))
}.foregroundColor(Color.black.opacity(0.5))
Text(name)
.font(.title)
.foregroundColor(.black)
Text(artist)
.foregroundColor(Color.black.opacity(0.5))
.font(.system(size: 19, weight: .thin))
}.frame(width: screenW/2.3)
}.frame(width: screenW - 30)
.padding(.top, 150)
.padding(.horizontal)
HStack(spacing: 18){
Button(action: {
}, label: {
ZStack{
Color.black
VStack{
Image(systemName: "play.circle")
Text("Odtwarzaj")
}.font(.system(size: 18))
.foregroundColor(.white)
}.frame(width: screenW/2.3, height: 50, alignment: .center)
.cornerRadius(10)
.shadow(radius: 15)
}).padding(.leading)
Button(action: {
}, label: {
ZStack{
Color.black.opacity(0.05)
VStack{
Image(systemName: "shuffle")
Text("Lososwo")
}.font(.system(size: 18))
.foregroundColor(.black)
}.frame(width: screenW/2.3, height: 50, alignment: .center)
.cornerRadius(10)
.shadow(radius: 15)
}).padding(.trailing)
}.frame(width: screenW - 30)
.padding(.horizontal)
ScrollView(.vertical, showsIndicators: false){
LazyVStack(alignment: .leading){
ForEach(bede_zycData.bede_zyc, id:\.self){ data in
NavigationLink(destination:
PlayerViewBedeZyc(bede_zycData: data, album: name, image: image, artist: artist)
, label: {
HStack{
Text(data.number)
.foregroundColor(Color.black.opacity(0.9))
.font(.system(size: 20))
.padding(.horizontal)
VStack(alignment: .leading){
Text(data.name)
.foregroundColor(Color.black)
.font(.system(size: 20))
Text(artist)
.foregroundColor(Color.black.opacity(0.5))
.font(.system(size: 18))
}
Spacer()
Text("2:36")
.foregroundColor(Color.black.opacity(0.5))
.font(.system(size: 18))
.padding()
}.padding()
})
}
}.padding(.top)
}
}.padding(.horizontal)
.edgesIgnoringSafeArea(.top)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.mode1.wrappedValue.dismiss()
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.black)
.font(.system(size: 28))
}))
}
}
struct PlayerViewBedeZyc : View {
#Environment(\.presentationMode) var presentationMode1
#State var bede_zycData : Bede_zycInfo
#State var album : String
#State var image : String
#State var artist : String
#State var player = AVPlayer()
#State var isPlaying : Bool = false
var body: some View{
VStack(alignment: .center){
Text(album)
.bold()
.padding(.top, 90)
.padding()
Image(image)
.resizable()
.scaledToFit()
.cornerRadius(10)
.shadow(radius: 20)
.frame(width: screenW/1.3, height: screenW/1.3)
.padding()
Text(bede_zycData.name)
.font(.title)
.bold()
.multilineTextAlignment(.center)
.padding()
Text(artist)
.foregroundColor(Color.black.opacity(0.5))
.font(.system(size: 18))
.padding(.bottom, 50)
HStack(spacing: 20){
Button(action: {
}, label: {
Image(systemName: "shuffle")
}).foregroundColor(Color.black.opacity(0.4))
.font(.system(size: 22))
Button(action: {
}, label: {
Image(systemName: "backward.end")
}).foregroundColor(Color.black)
.font(.system(size: 40))
Button(action: {
}, label: {
Image(systemName: "play.circle")
}).foregroundColor(Color.black)
.font(.system(size: 80))
Button(action: {
playPause()
}, label: {
Image(systemName: "forward.end")
}).foregroundColor(Color.black)
.font(.system(size: 40))
Button(action: {
}, label: {
Image(systemName: "repeat")
}).foregroundColor(Color.black.opacity(0.4))
.font(.system(size: 22))
}
Spacer()
}.onAppear(){
playSong()
}
.edgesIgnoringSafeArea(.top)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
presentationMode1.wrappedValue.dismiss()
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(.black)
.font(.system(size: 28))
}))
}
func playSong(){
let storage = Storage.storage().reference(forURL: self.bede_zycData.url)
storage.downloadURL { (url, error) in
if error != nil {
print(error!)
}else{
do{
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
}catch{
}
player = AVPlayer(playerItem: AVPlayerItem(url: url!))
}
}
}
func playPause(){
self.isPlaying.toggle()
if isPlaying == false{
player.pause()
}else{
player.play()
}
}
func next(){
}
}
This is data struct:
import SwiftUI
import Firebase
struct Bede_zycInfo : Hashable {
var id = UUID()
var name : String
var number : String
var url : String
}
class Bede_zycData : ObservableObject{
#Published public var bede_zyc = [Bede_zycInfo]()
func loadData(){
Firestore.firestore().collection("Bede_zyc").order(by: "number", descending: false).getDocuments { (snapshot, error) in
if error == nil {
for doc in snapshot!.documents{
let name = doc.data()["name"] as? String ?? "error"
let number = doc.data()["number"] as? String ?? "number"
let url = doc.data()["url"] as? String ?? "error?"
self.bede_zyc.append(Bede_zycInfo(name: name, number: number, url: url))
}
}else{
print(error)
}
}
}
}
And #main code:
import SwiftUI
import Firebase
#main
struct testApp: App {
let bede_zycdata = Bede_zycData()
init(){
FirebaseApp.configure()
bede_zycdata.loadData()
}
var body: some Scene {
WindowGroup {
ZespolView(bede_zycData: bede_zycdata)
}
}
}
HELP PLEASE!!!

Resources