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.
Related
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")
}
}
}
}
There is a button in the TabBar, this button should open the camera
"Button(action: {...}, label: { ..."
The camera code is written in "CameraView" and "CameraModel" :
struct CameraView: View {
#StateObject var camera = CameraModel()
var body: some View {
ZStack{
// Camera preview...
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack{
if camera.isTaken{
HStack {
Spacer()
Button(action: {}, label: {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.foregroundColor(.black)
.padding()
.background(Color.white)
.clipShape(Circle())
})
.padding(.trailing,10)
}
}
Spacer()
HStack{
if camera.isTaken{
Button(action: {}, label: {
Text("Save")
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical,10)
.padding(.horizontal,20)
.background(Color.white)
.clipShape(Capsule())
})
.padding(.leading)
Spacer()
}
else{
Button(action: {camera.isTaken.toggle()}, label: {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 65, height: 65)
Circle()
.stroke(Color.white,lineWidth: 2)
.frame(width: 75, height: 75)
}
})
}
}
.frame(height: 75)
}
}
.onAppear(perform: {
camera.Check()
})
}
}
class CameraModel: ObservableObject{
#Published var isTaken = false
#Published var session = AVCaptureSession()
#Published var alert = false
// since were going to read pic data....
#Published var output = AVCapturePhotoOutput()
// preview....
#Published var preview : AVCaptureVideoPreviewLayer!
func Check(){
// first checking camerahas got permission...
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setUp()
return
// Setting Up Session
case .notDetermined:
// retusting for permission....
AVCaptureDevice.requestAccess(for: .video) { (status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp(){
// setting up camera...
do{
// setting configs...
self.session.beginConfiguration()
// change for your own...
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
let input = try AVCaptureDeviceInput(device: device!)
// checking and adding to session...
if self.session.canAddInput(input){
self.session.addInput(input)
}
// same for output....
if self.session.canAddOutput(self.output){
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
}
catch{
print(error.localizedDescription)
}
}
}
// setting view for preview...
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera : CameraModel
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
// Your Own Properties...
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
// starting session
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
How to call the camera on click Button in TabBar "Button(action: {}, label: {
)"?
This is just an example of how I did it in the past (after all permissions etc... have been done).
Use your code to do the same.
struct ContentView: View {
#State var image: UIImage?
#State private var showCamera = false
var body: some View {
VStack {
Button(action: { self.showCamera.toggle() }) {
Image(systemName: "camera.circle").resizable().frame(width: 100, height: 100)
}
if image != nil {
Image(uiImage: image!).resizable().frame(width: 200, height: 200)
}
} // this is where it happens
.sheet(isPresented: $showCamera, onDismiss: {self.showCamera = false}) {
CameraViewController(photo: $image)
}
}
}
struct CameraViewController: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var photo: UIImage?
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraViewController>) -> UIImagePickerController {
let vc = UIImagePickerController()
vc.sourceType = .camera
vc.delegate = context.coordinator
return vc
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate, AVCapturePhotoCaptureDelegate {
var parent: CameraViewController
var captureSession: AVCaptureSession!
var capturePhotoOutput: AVCapturePhotoOutput!
var theCamera: AVCaptureDevice!
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
let photoQualityPrioritizationMode = AVCapturePhotoOutput.QualityPrioritization.speed
init(_ imagePickerController: CameraViewController) {
self.parent = imagePickerController
}
// called when a picture has been taken
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:[UIImagePickerController.InfoKey : Any]) {
guard let image = info[.originalImage] as? UIImage else {
print("No image found")
return
}
parent.photo = image // <--- the photo image
parent.presentationMode.wrappedValue.dismiss()
}
}
}
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!!!
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)
}
}
}
i have a bug, if i click on button before the animation before the card flip back. i think for me the best it would be to disable the button for 2 sec, but i made some research and didnt find anything!
struct CardBack: View {
var body: some View {
Image("back_card")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
}
}
struct ContentView: View {
#State var flipped = false
#State private var cardsFront = ["bigCard1", "bigCard2", "bigCard3", "bigCard4", "bigCard5" ]
#State private var cardBack = "back_card"
#State private var disablled = true
var body: some View {
VStack {
Spacer()
ZStack {
Image(flipped ? self.cardsFront.randomElement()! : self.cardBack)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
.rotation3DEffect(Angle(degrees: flipped ? 180 : 0 ), axis: (x: 0, y: 1, z: 0))
}
Spacer()
HStack {
Button(action: {
withAnimation(.spring()) {
self.flipped.toggle()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.spring()) {
self.flipped.toggle()
}
}
}) {
Image("circle")
.renderingMode(.original)
}
Button(action: {
}) {
Image("plus")
.renderingMode(.original)
}
iOS 13, Swift 5
You can set the button as disabled initially and then enable it using the same sort of logic I used here.
import SwiftUI
struct ContentView: View {
#State var changeColor = false
var body: some View {
TextView(changeColor: $changeColor)
}
}
struct TextView: View {
#Binding var changeColor: Bool
var body: some View {
Text("Hello World")
.foregroundColor(changeColor ? Color.black: Color.red)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.changeColor.toggle()
}
}
}
}
You are almost there, you just need to use the .appear tag in your code to do this.