How to link sing in to new page - firebase

Once I have signed into my app I display ext saying signed in but I want to link it to a new view in a separate file
How can I do this?
Sign in simplified page ...
struct ContentView: View {
#EnvironmentObject var viewModel: AppViewModel
var body: some View {
NavigationView{
if viewModel.signedIn{
VStack{
Text("Signed In")//I would like this to go to a homepage and not just say text
Button(action: {
viewModel.signOut()
}, label: {
Text("Sign Out")
.foregroundColor(.blue)
.background(Color(.green))
})
}
}
else{
SignInView()
}
}
.onAppear {
viewModel.signedIn = viewModel.isSignedIn
}
}
}
How can I active this whilst still being able to access my sign out button

Use NavigationLink programatically, from apple docs:
Optionally, you can use a navigation link to perform navigation
programmatically. You do so in one of two ways:
#State private var shouldShowPurple = false
Then you can modify the purple navigation link to bind to the state
variable:
NavigationLink(
"Purple",
destination: ColorDetail(color: .purple),
isActive: $shouldShowPurple)
In your case, isSignedIn should be used in isActive to trigger the navigation.

Related

In SwiftUI, how do I trigger a function after loading collection from Firebase?

My app loads a simple collection of .ics URLs from Firebase. I then download and parse each calendar file to save in an array of structs.
This code works:
struct Calendar: Identifiable, Codable {
#DocumentID var id: String?
var urlString: String = ""
var name: String = ""
var licenseNumber: String = ""
}
struct ScheduleView: View {
#FirestoreQuery(collectionPath: "calendars") var calendars: [Calendar]
#State var matches: [Match] = []
var body: some View {
NavigationView {
VStack {
List(calendars) { calendar in
Text (calendar.urlString)
}
List(matches) { match in
Text (match.dateString)
}
Spacer()
Button {
downloadMatchCalendars()
} label: {
Text ("Download calendars")
}
}
}
}
func downloadMatchCalendars() { /* code that correctly populates matches */ }
}
List(calendars) is immediately visible when I launch and it updates when the Firebase collection changes. But, I can't get List(matches) to update, too. It's blank until I click the button. And if a new calendar is added in Firebase, the new matches are not loaded/displayed.
I tried using NavigationView{}.onAppear(perform: dowloadMatchCalendars). I tried a didSet on my #FirestoreQuery. Neither worked.
How can I run downloadMatchCalendars after loading the calendars from Firebase.
(See next post for my second related question.)

How to bind an action to the navigationview back button?

Hi I would like to bind an action to the Back button in the navigationview toolbar, is it possible?
picture about the situation
var body: some View {
List {
ForEach(mainViewModel.items) { item in
NavigationLink(destination: EditTaskView(item: item)) {
HStack {
ListRowView(item: item)
You can't bind directly to the back button, but you can have the navigation link itself be activated based on state, and then listen to the change of the state value like so. Do note that this requires that you manage the setting of state to true (no auto tap like with the default initializer)
struct ContentView: View {
#State private var showingNavView = false
var body: some View {
NavigationView {
List {
NavigationLink("Sub View", isActive: $showingNavView) {
SubView()
}.onTapGesture {
showingNavView = true
}.onChange(of: showingNavView) { newValue in
print(newValue) // Will change to false when back is pressed
}
}
}
}
}
struct SubView: View {
var body: some View {
ZStack {
Color.green
Text("Cool Beans")
}
}
}

How to refresh view with fetched data - Firestore & SwiftUI

Short: The Images in my view are not updating after the first load. The URL remains the same as the previous loaded view, however the rest of the view that doesn't fetch a URL or data from storage is updated.
Full: I have two Views, a ListView and a DetailView.
In the ListView I display a list of type List. The detail view is supposed to show each Profile from List.profiles. I do this by storing each string uid in List.profiles and calling model.fetchProfiles to fetch the profiles for each list selected.
On the first selected List model.fetchProfiles returns the documents and model.profiles displays the data fine in the DetailView.
When first loading the DetailView the ProfileRow on appear is called and logs the profiles fetched. Then the ProfileRow loads the imageURL from the imagePath and uses it like to fetch the image.
Console: Load List1
CARD DID APPEAR: Profiles []
CARD DID APPEAR: SortedProfiles []
CARD ROW
CARD ROW DID APPEAR: Profiles profiles/XXXXXX/Profile/profile.png
CARD ROW DID APPEAR: SortedProfiles profiles/XXXXXX/Profile/profile.png
Get url from image path: profiles/XXXXXX/Profile/profile.png
Image URL: https://firebasestorage.googleapis.com/APPNAME/profiles%XXXXXXX
When selecting the second List from ListView the ProfileRow didAppear is not called due to;
if model.profiles.count > 0 {
print("CARD ROW DID APPEAR: Profiles \(model.profiles[0]. imgPath)")
print("CARD ROW DID APPEAR: Sorted \(model.sortedProfiles[0].imgPath)")
}
and won't ever again when selecting a List in ListView, however the rest of the profile data in the ProfileRow is displayed such as name so the data must be fetched.
The ImagePath is the same as the first view loading the exact same image. All other properties for the Profile such as name are loaded correctly.
Console: Load List2
CARD DID APPEAR: Profiles []
CARD DID APPEAR: SortedProfiles []
CARD ROW
Get url from image path: profiles/XXXXXX/Profile/profile.png
Image URL:
https://firebasestorage.googleapis.com/APPNAME/profiles%XXXXXXX
If I then navigate to List1 then the image for List2 appears, if I reselect List2 the image appears fine. The image show is correct on first load, and when selecting another list it always the one from before.
Can anyone help me out ?
First View
struct ListViw: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
ForEach(model.lists.indices, id: \.self) { index in
NavigationLink(
destination: DetailView()
.environmentObject(model)
.onAppear() {
model.fetchProfiles()
}
) {
ListRow(home:model.lists[index])
.environmentObject(model)
}
.isDetailLink(false)
}
}
}
}
DetailView Card
struct ProfilesCard: View {
#EnvironmentObject var model: Model
var body: some View {
VStack(alignment: .trailing, spacing: 16) {
if !model.sortedProfiles.isEmpty {
VStack(alignment: .leading, spacing: 16) {
ForEach(model.sortedProfiles.indices, id: \.self) { index in
ProfileRow(
name: "\(model.sortedProfiles[index].firstName) \(model.sortedProfiles[index].lastName)",
imgPath: model.sortedProfiles[index].imgPath,
index: index)
.environmentObject(model)
}
}
.padding(.top, 16)
}
}//End of Card
.modifier(Card())
.onAppear() {
print("CARD DID APPEAR: Profiles \(model.profiles)")
print("CARD DID APPEAR: SORTED \(model.sortedTenants)")
}
}
}
struct ProfileRow: View {
#EnvironmentObject var model: Model
#State var imageURL = URL(string: "")
var name: String
var imgPath: String
var index: Int
private func loadImage() {
print("load image: \(imgPath)")
DispatchQueue.main.async {
fm.getURLFromFirestore(path: imgPath, success: { (imgURL) in
print("Image URL: \(imgURL)")
imageURL = imgURL
}) { (error) in
print(error)
}
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .center, spacing: 12) {
KFImage(imageURL,options: [.transition(.fade(0.2)), .forceRefresh])
.placeholder {
Rectangle().foregroundColor(.gray)
}
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.cornerRadius(16)
// Profile text is always displayed correctly
Text(name)
.modifier(BodyText())
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.onAppear() {
print("CARD ROW")
// Crashes if check is not there
if model.profiles.count > 0 {
print("CARD ROW DID APPEAR: Profiles \(model.profiles[0]. imgPath)")
print("CARD ROW DID APPEAR: Sorted \(model.sortedProfiles[0].imgPath)")
}
loadImage()
}
}
}
Model
class Model: ObservableObject {
init() {
fetchData()
}
#Published var profiles: [Profile] = []
var sortedProfiles: [Profile] {return profiles.removeDuplicates }
#Published var list: List? {
didSet {
fetchProfiles()
}
}
func fetchData() {
if let currentUser = Auth.auth().currentUser {
email = currentUser.email!
db.collection("lists")
.whereField("createdBy", isEqualTo: currentUser.uid)
.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.lists = documents.compactMap { queryDocumentSnapshot -> List? in
return try? queryDocumentSnapshot.data(as: List.self)
}
}
}
}
func fetchProfiles() {
profiles.removeAll()
for p in list!.profiles {
firestoreManager.fetchProfile(uid: t, completion: { [self] profile in
profiles.append(profile)
})
}
}
}
Update
What I have tried so far is to use didSet for the ImgPath or ImgURL but still not luck. Also have tried using model.profiles directly.
In all callbacks with Firestore API make assignment for published or state properties on main queue, because callback might be called on background queue.
So, assuming data is returned and parsed correctly, here is as it should look like
for p in list!.profiles {
firestoreManager.fetchProfile(uid: t, completion: { [self] profile in
DispatchQueue.main.async {
profiles.append(profile)
}
})
}
also I would recommend to avoid same naming for your custom types with SDK types - there might be very confusing non-obvious errors
// List model below might conflict with SwiftUI List
return try? queryDocumentSnapshot.data(as: List.self)
As per my knowledge its not the problem from firebase end, because the ones data fetched the new data is updated. You are facing problem of image caching. Caching is a technique that stores a copy of a given resource. So when the image is loaded for first time it get cached and whenever you are reloading images are displayed from cache instead of loading from URL. This is done for more network usage.
You can programatically clear cache by adding following code before your image loading.
Alamofire uses NSURLCache in the background so you just have to call:
NSURLCache.sharedURLCache().removeAllCachedResponses()
Update for Swift 4.1
URLCache.shared.removeAllCachedResponses()

swiftui open view from menu button

I am trying to do something very basic, but I have been through the official tutorials, and through dozens of stack overflow posts. I am trying to open an activity from a menu button. I do want the standard navigation back arrow when I do this.
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {
Activities()
}) {
Label("Activities", systemImage: "doc")
}
"Activities()" is my view. When I do this nothing happens. I have read in other posts that you cannot have a navigation link in a menu either, which would be fine if that worked. How do I programmatically open up a view so the back arrow works?
Thank you
In this case where you want to open a view but can't use a NavigationLink directly you can use it in another place and activate it programmatically from the button via a State property:
#State private var isShowingDetailView = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
NavigationLink(destination: Activities(), isActive: $isShowingDetailView) {
EmptyView()
}
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {
isShowingDetailView = true
}) {
Label("Activities", systemImage: "doc")
}
}
}
}
}
}

SWIFTUI. How to permanently change the color of the buttons after clicking?

I am building a quiz app. A question; 3 answers. The answers are in separate buttons.
I managed the change the color of the buttons when clicked. Eg from gray to green/red. But after the click the button goes back to gray.
Is there a way to keep the changed colors after the click, like a hyperlink in HTML?
This is the style code:
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(configuration.isPressed ? Color.red : Color.gray)
.cornerRadius(10.0)
You can declare your ButtonStyle like this:
public struct SelectedButtonStyle: ButtonStyle {
#Binding var isSelected: Bool
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(isSelected ? Color.red : Color.gray)
.cornerRadius(10.0)
}
}
and then in the view, have a state for the selction:
#State var isSelected = false
and then you can either declare your button like this to have it selected once and forever stay selected:
Button("Tap") {
self.isSelected = true
} .buttonStyle(SelectedButtonStyle(isSelected: $isSelected))
or you can declare it like this to be able to deselect it as well:
Button("Tap") {
self.isSelected.toggle()
} .buttonStyle(SelectedButtonStyle(isSelected: $isSelected))
try something like this, once isPressed becomes true it won't become false again, so the button will stay gray
struct ContentView: View {
#State private var isPressed = false
var body: some View {
Button(action: {
self.isPressed = true
}, label: {
Text("Button").accentColor(isPressed ? Color.gray : Color.blue)
})
}
}

Resources