I'm trying to allow the FileManager to check if the matching Image Selected is saved. When saved, it needs to update the views on both the MainScreenView and BadgeScreenView. I am getting a error that "Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update." and "Result of 'BadgeScreenView' initializer is unused" after the image is selected and checked to see if it is saved at the right locations.
var ContentViewBadge = UIImage(systemName: "questionmark")!
var fileURL: URL?
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
fileURL = furl
try ContentViewBadge.pngData()?.write(to: furl)
print("Image \(ContentViewBadge) is saved to \(furl)")
} catch {
print("could not create imageFile")
}
let finding = fileURL
let fileExists = FileManager().fileExists(atPath: finding!.path)
if fileExists {
#State var IsTrue: Bool = true
BadgeScreenView(TrueBadge: $IsTrue)
//Change State variable "TrueBadge" here
print("Found something!")
}
}
import SwiftUI
import Foundation
var IsDone = false
struct BadgeScreenView: View {
#Binding var TrueBadge: Bool //Need help switching this Binding to true
#State private var ComplianceBadgeIsPicking = UIImage(named: "BlankComplianceBadge")!
#State private var isShwoingPhotoPicker = false
#State private var ShowInstruction = false
#State private var AlertToReplaceBade = false
var body: some View {
//The beginning
if TrueBadge {
Color("MainBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Text("Clearance Status")
.font(.title)
.fontWeight(.semibold)
.offset(y: -15)
.foregroundColor(.white)
Text("Vaccine Compliant")
.foregroundColor(.white)
.bold()
.font(.system(size: 30))
Image(uiImage: ContentViewBadge)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Button(action: {
AlertToReplaceBade.toggle()
}) {
Image(systemName: "trash" )
Text("Remove")
}
.foregroundColor(.white)
.padding()
.background(Color.red)
.cornerRadius(15)
.offset(y: 13)
}.alert(isPresented: $AlertToReplaceBade, content: {
Alert(title: Text("Are you sure you would like to remove your current badge?"),
message: Text("Remeber that this badge is and will be permanently removed"),
primaryButton: .default(Text("Yes"), action: {
// Somehow need to remove the image and activate the UIImagePickerController
isShwoingPhotoPicker.toggle()
}), secondaryButton: .cancel(Text("No, I do not")))
}).sheet(isPresented: $isShwoingPhotoPicker, content: {
PhotoPicker(Badge: $ComplianceBadgeIsPicking)
})
)}
else {
Color("ExpiredBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Image(systemName: "person.crop.circle.badge.questionmark.fill")
.font(.system(size:150))
.offset(y: -10)
.foregroundColor(.black)
Text("Compliance Badge")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.black)
.offset(y: -2)
Text("You do not have a current vaccine compliant badge. Please upload one that shows you are vaccine compliant or within 'green' status")
.font(.system(size: 15))
.foregroundColor(.black)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.frame(width: 270, height: 140, alignment: .center)
.offset(y: -26)
Button(action: {
ShowInstruction.toggle()
}) {
Image(systemName: "questionmark.circle")
Text("How to upload")
.bold()
.font(.system(size:20))
}
.offset(y: -40)
Button(action: {
isShwoingPhotoPicker.toggle()
}) {
Image(systemName: "square.and.arrow.up")
Text("Upload Badge")
.bold()
.font(.system(size:20))
}
.offset(y: -10)
}.sheet(isPresented: $ShowInstruction, content: {
Instruction()
})
.sheet(isPresented: $isShwoingPhotoPicker, content: {
PhotoPicker(Badge: $ComplianceBadgeIsPicking)
})
.accentColor(.black)
)
}
//The End
}
}
There's not enough for a minimal reproducible example in your code, so I had to make some guesses here, but this is the gist of what I'd expect things to look like. Note that saveImage is inside the View and thus has access to change the state.
It's not clear to me, though, where you call saveImage (you don't do it anywhere in your included code), which could effect things further.
let contentViewBadge = UIImage(systemName: "questionmark")!
struct BadgeScreenView: View {
#Binding var trueBadge: Bool
#State private var complianceBadgeIsPicking = UIImage(named: "BlankComplianceBadge")!
#State private var isShowingPhotoPicker = false
#State private var showInstruction = false
#State private var alertToReplaceBade = false
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
try contentViewBadge.pngData()?.write(to: furl)
print("Image \(contentViewBadge) is saved to \(furl)")
let fileExists = FileManager().fileExists(atPath: furl.path)
if fileExists {
trueBadge = true
print("Found something!")
}
} catch {
print("could not create imageFile")
}
}
var body: some View {
//The beginning
if trueBadge {
Color("MainBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Text("Clearance Status")
.font(.title)
.fontWeight(.semibold)
.offset(y: -15)
.foregroundColor(.white)
Text("Vaccine Compliant")
.foregroundColor(.white)
.bold()
.font(.system(size: 30))
Image(uiImage: contentViewBadge)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Button(action: {
alertToReplaceBade.toggle()
}) {
Image(systemName: "trash" )
Text("Remove")
}
.foregroundColor(.white)
.padding()
.background(Color.red)
.cornerRadius(15)
.offset(y: 13)
}.alert(isPresented: $alertToReplaceBade, content: {
Alert(title: Text("Are you sure you would like to remove your current badge?"),
message: Text("Remeber that this badge is and will be permanently removed"),
primaryButton: .default(Text("Yes"), action: {
// Somehow need to remove the image and activate the UIImagePickerController
isShowingPhotoPicker.toggle()
}), secondaryButton: .cancel(Text("No, I do not")))
}).sheet(isPresented: $isShowingPhotoPicker, content: {
PhotoPicker(Badge: $complianceBadgeIsPicking)
})
)}
else {
Color("ExpiredBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Image(systemName: "person.crop.circle.badge.questionmark.fill")
.font(.system(size:150))
.offset(y: -10)
.foregroundColor(.black)
Text("Compliance Badge")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.black)
.offset(y: -2)
Text("You do not have a current vaccine compliant badge. Please upload one that shows you are vaccine compliant or within 'green' status")
.font(.system(size: 15))
.foregroundColor(.black)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.frame(width: 270, height: 140, alignment: .center)
.offset(y: -26)
Button(action: {
showInstruction.toggle()
}) {
Image(systemName: "questionmark.circle")
Text("How to upload")
.bold()
.font(.system(size:20))
}
.offset(y: -40)
Button(action: {
isShwoingPhotoPicker.toggle()
}) {
Image(systemName: "square.and.arrow.up")
Text("Upload Badge")
.bold()
.font(.system(size:20))
}
.offset(y: -10)
}.sheet(isPresented: $showInstruction, content: {
Instruction()
})
.sheet(isPresented: $isShowingPhotoPicker, content: {
PhotoPicker(Badge: $complianceBadgeIsPicking)
})
.accentColor(.black)
)
}
//The End
}
}
An alternative (or addition) to putting savingImage inside the view would be to explicitly pass a binding to it. The function would look like this:
func saveImage(binding: Binding<Bool>) {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
try contentViewBadge.pngData()?.write(to: furl)
print("Image \(contentViewBadge) is saved to \(furl)")
let fileExists = FileManager().fileExists(atPath: furl.path)
if fileExists {
binding.wrappedValue = true
print("Found something!")
}
} catch {
print("could not create imageFile")
}
}
And you would call it like this:
saveImage(binding: $trueBadge)
Note: I changed your variable names to fit the Swift conventions of using lowercase letters to start variable/property names
Related
I am trying to assign variable "furl" to #State var fileurl: URL? in saveImage() of my MainScreenView(). In saveImage Print(furl) shows the URL, but after assigning fileURL = furl, print(fileurl) returns nil, that's the issue. Removing the optional doesn't allow it to build as:
if let furl = fileurl {
let data = try Data(contentsOf: furl)
if let img = UIImage(data: data) {
return img
}.
of loadImage()Throws an error of "Initializer for conditional binding must have Optional type, not 'URL'". I'm unsure what to do in this case.
Here's MainScreenView
import SwiftUI
import SafariServices
struct MainScreenView: View {
#State var isfound = true
#State var failed = false
#State var ShowInstruction = false
#State var fileurl: URL? //I'm guessing since this is an optional, it's not allow it to do something correctly?
#State var ShowBadge = false
#State var ShowPortal = false
#State var ShowDetails = false
#State var alert = false
#State var listalert = false
#State var truebadge: Bool = false
var body: some View {
NavigationView {
Color("BackgroundMain")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Button {
ShowBadge = true
} label: {
if truebadge != true{
VStack{
Image(systemName: "person")
.font(.system(size: 120))
.foregroundColor(Color.white)
.padding()
Text("expired compliance")
.font(.system(size: 30))
.foregroundColor(Color.white)
}.offset(y: -30)
}
else {
VStack{
Image(systemName: "person")
.font(.system(size: 120))
.foregroundColor(Color("MainBadgeScreen"))
.padding()
Text("Show Complaince")
.font(.system(size: 30))
.foregroundColor(Color("MainBadgeScreen"))
}.offset(y: -30)
}
}
Button {
} label: {
HStack {
Image(systemName: "lanyardcard")
Text("Student ID")
}
.font(.system(size: 23))
.foregroundColor(.white)
.frame(width: 200, height: 65, alignment: .center)
.background(Color.blue)
.cornerRadius(15)
.offset(y: -10)
}
Button {
listalert.toggle()
} label: {
HStack{
Image(systemName: "gear.circle")
Text("Settings")
}
.font(.system(size: 23))
.foregroundColor(.white)
.frame(width: 200, height: 65, alignment: .center)
.background(Color.gray)
.cornerRadius(15)
}
}
).sheet(isPresented: $listalert, content: {
NavigationView{
List {
Button(action: {
ShowPortal = true
}) {
HStack{
Text("Student Health Portal")
Spacer()
Image(systemName: "heart.fill")
}.foregroundColor(Color.red)
.font(.system(size: 20))}
Button(action: {
ShowInstruction = true
}) {
HStack{
Text("Instructions")
Spacer()
Image(systemName: "questionmark.circle")
}.font(.system(size: 20))}
Button(action: {
ShowDetails = true
}) {
HStack{
Text("About")
Spacer()
Image(systemName: "info.circle")
}.font(.system(size: 20))}
}
.navigationBarTitle(Text("Settings"), displayMode: .inline)
}
.sheet(isPresented: $ShowPortal, content: {
safari()
})
.sheet(isPresented: $ShowInstruction, content: {
Instruction()
})
.sheet(isPresented: $ShowDetails, content: {
Details()
})
})
.sheet(isPresented: $ShowBadge, content: {
BadgeScreenView(complianceview: loadImage(), truebadge: truebadge)
})
.navigationTitle("Home")
.navigationBarTitleDisplayMode(.large)
}
}
func saveImage(binding: Binding<Bool>, image: UIImage){
//Need to set #State var truebadge to 'true'
let finalcompliance = image.self.pngData()
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
print(furl) // shows url from furl
fileurl = furl // assigning furl to fileurl allows it to be assigned. But ends up becoming nil
print(fileurl) //nil value; doesn't allow the image to load in loadimage() even though assigned to furl
try finalcompliance?.write(to: furl)
} catch{
print("could not create imageFile")
}
}
func loadImage() -> UIImage {
do {
print("showing the image!")
if let furl = fileurl {
let data = try Data(contentsOf: furl)
if let img = UIImage(data: data) {
return img
}
}
} catch {
print("error: \(error)") // todo
}
return UIImage()
}
}
struct safari : UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<safari>) -> SFSafariViewController{
let controller = SFSafariViewController(url: URL(string: "https://patientportal.bowiestate.edu/login_directory.aspx")!)
return controller
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<safari>) {
}
}
Good Evening,
I have a question, I need to show an error when a user enters a wrong email or enters a wrong password. What do I need to add in my code?
I am new to software development and Sam not so familiar with swift ui could someone help me?
#State var email = ""
#State var password = ""
var body: some View {
Image("ISD-Logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 185, height: 140)
.clipped()
.padding(.bottom, 75)
VStack {
TextField("Email", text: $email)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: { login() }) {
Text("Sign in")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.black)
.cornerRadius(35.0)
}
}
.padding()
}
// Login and error message
func login() {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
print(error?.localizedDescription ?? "")
} else {
print("success")
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
What kind of alert do you want?
You can do that:
struct ContentView: View {
#State private var showingAlert = false
var body: some View {
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
}
}
You can try like this. I would suggest you go to SWIFT UI basics first
#State var email = ""
#State var password = ""
var body: some View {
Image("ISD-Logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 185, height: 140)
.clipped()
.padding(.bottom, 75)
VStack {
TextField("Email", text: $email)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: { login() }) {
Text("Sign in")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.black)
.cornerRadius(35.0)
}
}
.padding()
}
// Login and error message
func login() {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
self.isAlert = true
print(error?.localizedDescription ?? "")
} else {
print("success")
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
when I change the selecteddrive by using a button(ontapGesture),it prints "value changed",but when I use the Picker to change the value, the didset function did not work.
enum WheelDrive: String, CaseIterable, Identifiable {
case frontdrive
case reardrive
case Fourwheeldrive
case Neutral
var id: String { self.rawValue }
}
#State var selecteddrive = WheelDrive.Fourwheeldrive {
willSet(newValue){ print("value changed")}
}
Picker(selection: $selecteddrive, label: Text("WheelDrive")) {
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}
Use custom binding
Picker(selection: Binding(get: {selecteddrive}, set: {selecteddrive = $0}), label: Text("WheelDrive")) { //<-- Here
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}
Or you can use .onChange for capturing value.
Picker(selection: $selecteddrive, label: Text("WheelDrive")) {
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}
.onChange(of: selecteddrive, perform: { (selecteddrive) in //<- Here
print(selecteddrive)
})
I have a login view that does an http request . Once we get the http response we know whether the user can go to the next view . I am wondering how can I trigger the next view without click ? I am not looking to do a sheet since I want to get the full screen mode . I have been looking at this Go to a new view using SwiftUI but no luck . From my code below when I click on Press on me navigationLink I can go to the correct view, however I need that same functionality to work without clicking in the http response below where it says decode.status == 1 because the user has authenticated successfully .
struct ContentView: View {
#State var email: String = ""
#State var password: String = ""
#State var message: String = ""
#State var status: Int = -10
#State var showActive = true
var body: some View {
NavigationView {
ZStack {
Color(UIColor.systemGroupedBackground)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
NavigationLink(destination: MainView()) {
Text("Press on me")
}.buttonStyle(PlainButtonStyle()) // This works when clicked
TextField("Email", text: $email)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
SecureField("Password", text: $password)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
Button(action: {
guard let url = URL(string:ConnectionString+"profile/login") else { return }
let parameter = "email=\(self.email)&password=\(self.password)"
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
URLSession.shared.dataTask(with:request, completionHandler: {(data, response, error) in
if let decode = try? JSONDecoder().decode(ProfileCodable.self, from: data!)
{
self.status = decode.status ?? -10
self.message = decode.message ?? ""
if decode.status == 0 {
print("Invalid Credentials")
} else if decode.status == 1 {
// ** Go to next View here **
} else if decode.status == -1 {
print("Error")
}
} else {
print("No Response")
}
}).resume()
}) {
Text("Login")
.padding(10)
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 22))
.foregroundColor(Color(UIColor(hexString: "#006622")))
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.black, lineWidth: 1))
}.padding([.top],40)
if self.status == 0 {
Text(self.message)
.foregroundColor(.red)
.font(.system(size: 20))
.padding([.top],30)
}
Spacer()
}.padding()
}
}
}
}
Try this (scratchy - not tested because not compilable due to absent dependencies), so adapt at your side:
struct ContentView: View {
#State var email: String = ""
#State var password: String = ""
#State var message: String = ""
#State var status: Int = -10
#State var showActive = false // << seems this state, so false
var body: some View {
NavigationView {
ZStack {
Color(UIColor.systemGroupedBackground)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
TextField("Email", text: $email)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
SecureField("Password", text: $password)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
Button(action: {
guard let url = URL(string:ConnectionString+"profile/login") else { return }
let parameter = "email=\(self.email)&password=\(self.password)"
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
URLSession.shared.dataTask(with:request, completionHandler: {(data, response, error) in
if let decode = try? JSONDecoder().decode(ProfileCodable.self, from: data!)
{
self.status = decode.status ?? -10
self.message = decode.message ?? ""
if decode.status == 0 {
print("Invalid Credentials")
} else if decode.status == 1 {
self.showActive = true // << here !!
} else if decode.status == -1 {
print("Error")
}
} else {
print("No Response")
}
}).resume()
}) {
Text("Login")
.padding(10)
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 22))
.foregroundColor(Color(UIColor(hexString: "#006622")))
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.black, lineWidth: 1))
}.padding([.top],40)
.background(
// activated by state programmatically !!
NavigationLink(destination: MainView(), isActive: $self.showActive) {
EmptyView() // << just hide
}.buttonStyle(PlainButtonStyle())
)
if self.status == 0 {
Text(self.message)
.foregroundColor(.red)
.font(.system(size: 20))
.padding([.top],30)
}
Spacer()
}.padding()
}
}
}
}
Simply use the isActive property of navigation link. It would look like this:
NavigationLink(destination: MainView(), isActive: $mainViewActive) {
Text("Press on me")
}.buttonStyle(PlainButtonStyle())
and you should also declare the variable in your view:
#State var mainViewActive = false
then on successful login simply change the value to true. If you also do not want to display an actual link use EmptyView() as wrapper. So it would look like this:
NavigationLink(destination: MainView(), isActive: $mainViewActive) {
EmptyView()
}
I'm trying to build an Instagram clone app using SwiftUI.
I'm fetching the data through Firebase and trying to achieve a UI update every time the data in the server changes.
For some reason, when I first open the app and fetch the data, the body of my view gets called, but the UI doesn't change. I even put a breakpoint and saw the body gets called and contains the correct information, it's just the UI which doesn't get updated.
I have a few tabs in my app, and when I switch to another tab (which doesn't contain anything but a Text yet), suddenly the UI does gets updated.
Please see the gif below:
Here is my code:
HomeView:
struct HomeView: View {
#ObservedObject private var fbData = firebaseData
var body: some View {
TabView {
//Home Tab
NavigationView {
ScrollView(showsIndicators: false) {
ForEach(self.fbData.posts.indices, id: \.self) { postIndex in
PostView(post: self.$fbData.posts[postIndex])
.listRowInsets(EdgeInsets())
.padding(.vertical, 5)
}
}
.navigationBarTitle("Instagram", displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
print("Camera btn pressed")
}, label: {
Image(systemName: "camera")
.font(.title)
})
, trailing:
Button(action: {
print("Messages btn pressed")
}, label: {
Image(systemName: "paperplane")
.font(.title)
})
)
} . tabItem({
Image(systemName: "house")
.font(.title)
})
Text("Search").tabItem {
Image(systemName: "magnifyingglass")
.font(.title)
}
Text("Upload").tabItem {
Image(systemName: "plus.app")
.font(.title)
}
Text("Activity").tabItem {
Image(systemName: "heart")
.font(.title)
}
Text("Profile").tabItem {
Image(systemName: "person")
.font(.title)
}
}
.accentColor(.black)
.edgesIgnoringSafeArea(.top)
}
}
FirebaseData:
let firebaseData = FirebaseData()
class FirebaseData : ObservableObject {
#Published var posts = [Post]()
let postsCollection = Firestore.firestore().collection("Posts")
init() {
self.fetchPosts()
}
//MARK: Fetch Data
private func fetchPosts() {
self.postsCollection.addSnapshotListener { (documentSnapshot, err) in
if err != nil {
print("Error fetching posts: \(err!.localizedDescription)")
return
} else {
documentSnapshot!.documentChanges.forEach { diff in
if diff.type == .added {
let post = self.createPostFromDocument(document: diff.document)
self.posts.append(post)
} else if diff.type == .modified {
self.posts = self.posts.map { (post) -> Post in
if post.id == diff.document.documentID {
return self.createPostFromDocument(document: diff.document)
} else {
return post
}
}
} else if diff.type == .removed {
for index in self.posts.indices {
if self.posts[index].id == diff.document.documentID {
self.posts.remove(at: index)
}
}
}
}
}
}
}
private func createPostFromDocument(document: QueryDocumentSnapshot) -> Post {
let data = document.data()
let id = document.documentID
let imageUrl = data["imageUrl"] as! String
let authorUsername = data["authorUsername"] as! String
let authorProfilePictureUrl = data["authorProfilePictureUrl"] as! String
let postLocation = data["postLocation"] as! String
let postDescription = data["postDescription"] as! String
let numberOfLikes = data["numberOfLikes"] as! Int
let numberOfComments = data["numberOfComments"] as! Int
let datePosted = (data["datePosted"] as! Timestamp).dateValue()
let isLiked = data["isLiked"] as! Bool
return Post(id: id, imageUrl: imageUrl, authorUsername: authorUsername, authorProfilePictureUrl: authorProfilePictureUrl, postLocation: postLocation, postDescription: postDescription, numberOfLikes: numberOfLikes, numberOfComments: numberOfComments, datePosted: datePosted, isLiked: isLiked)
}
}
If you need me to post more code please let me know.
Update:
PostView:
struct PostView: View {
#Binding var post: Post
var body: some View {
VStack(alignment: .leading) {
//Info bar
HStack {
WebImage(url: URL(string: post.authorProfilePictureUrl))
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 2) {
Text(post.authorUsername).font(.headline)
Text(post.postLocation)
}
Spacer()
Button(action: {
print("More options pressed")
}, label: {
Image(systemName: "ellipsis")
.font(.title)
.foregroundColor(.black)
}).buttonStyle(BorderlessButtonStyle())
}
.padding(.horizontal)
//Main Image
WebImage(url: URL(string: post.imageUrl))
.resizable()
.aspectRatio(contentMode: .fit)
//Tools bar
HStack(spacing: 15) {
Button(action: {
self.post.isLiked.toggle()
print("Like btn pressed")
}, label: {
Image(systemName: post.isLiked ? "heart.fill" : "heart")
.font(.title)
.foregroundColor(.black)
}).buttonStyle(BorderlessButtonStyle())
Button(action: {
print("Comments btn pressed")
}, label: {
Image(systemName: "message")
.font(.title)
.foregroundColor(.black)
}).buttonStyle(BorderlessButtonStyle())
Button(action: {
print("Share btn pressed")
}, label: {
Image(systemName: "paperplane")
.font(.title)
.foregroundColor(.black)
}).buttonStyle(BorderlessButtonStyle())
Spacer()
Button(action: {
print("Bookmark btn pressed")
}, label: {
Image(systemName: "bookmark")
.font(.title)
.foregroundColor(.black)
}).buttonStyle(BorderlessButtonStyle())
}.padding(8)
Text("Liked by \(post.numberOfLikes) users")
.font(.headline)
.padding(.horizontal, 8)
Text(post.postDescription)
.font(.body)
.padding(.horizontal, 8)
.padding(.vertical, 5)
Button(action: {
print("Show comments btn pressed")
}, label: {
Text("See all \(post.numberOfComments) comments")
.foregroundColor(.gray)
.padding(.horizontal, 8)
}).buttonStyle(BorderlessButtonStyle())
Text(post.datePostedString)
.font(.caption)
.foregroundColor(.gray)
.padding(.horizontal, 8)
.padding(.vertical, 5)
}
}
}
Post:
struct Post : Identifiable, Hashable {
var id: String
var imageUrl: String
var authorUsername: String
var authorProfilePictureUrl: String
var postLocation: String
var postDescription: String
var numberOfLikes: Int
var numberOfComments: Int
var datePostedString: String
var isLiked: Bool
init(id: String, imageUrl: String, authorUsername: String, authorProfilePictureUrl: String, postLocation: String, postDescription : String, numberOfLikes: Int, numberOfComments: Int, datePosted: Date, isLiked: Bool) {
self.id = id
self.imageUrl = imageUrl
self.authorUsername = authorUsername
self.authorProfilePictureUrl = authorProfilePictureUrl
self.postLocation = postLocation
self.postDescription = postDescription
self.numberOfLikes = numberOfLikes
self.numberOfComments = numberOfComments
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd, yyyy"
self.datePostedString = dateFormatter.string(from: datePosted)
self.isLiked = isLiked
}
}
Thank you!
The problem is that when the app starts your array is empty, and the ScrollView stops updating, you can replace it for a VStack and it will work (just for testing).
The solution is to wrap the ForEach(or the ScrollView) with a condition, like this:
if (fbData.posts.count > 0) {
ForEach(self.fbData.posts.indices, id: \.self) { postIndex in
PostView(post: self.$fbData.posts[postIndex])
.listRowInsets(EdgeInsets())
.padding(.vertical, 5)
}
}