SwiftUI navigationBarTitle - displayMode: .inline - Crash - navigationbar

I have a problem with SwiftUI. I am creating a list with a navigation bar, and I want to set navigation bar mode to inline, I don't want it to be large which is by default. But when I set navigation bar title mode to inline, the app crashes.
struct User {
var index: Int
var name: String
}
struct ContentView : View {
var users: [User] = [
User(index: 0, name: "Peter"),
User(index: 1, name: "Marko"),
User(index: 2, name: "John")]
var body: some View {
NavigationView {
List(users.identified(by: \.index)) {
UserRow(user: $0)
}
.navigationBarTitle(Text("Users"), displayMode: .inline)
}
}
}
struct UserRow: View {
var user: User
var body: some View {
Text(user.name)
}
}
I suppose that it is a SwiftUI bug.
Does someone know what could be the problem? Thanks in advance.

Looks like it is fixed in Xcode 11.4 beta 2

Related

swiftUI tabView pagetabviewstyle - button to next is not working

import SwiftUI
GuideImageView
currentPage mean 1VStack which is in text and images from array from guidelists
struct GuideImageView: View {
#State var currentPage: Int = 0
var body: some View {
VStack{
TabView(selection: $currentPage){
ForEach(guidelists){i in
VStack{
Text(i.explain)
Image(i.image)
.resizable()
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) //page처럼 구현 + ...을 안보이게함
Button("Next") {
if currentPage == 3 {
currentPage = 0
//return
}else{
currentPage += 1
}
}
}
}
}
Struct GuideList
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id = UUID() //UUID = 고유식별자
let image: String
let explain: String
}
let guidelists
let guidelists = [
GuideList(image: "image1",explain: "explain1." ),
GuideList(image: "image2",explain: "explain2." ),
GuideList(image: "image3",explain: "explain3." )
]
GuideImageView_Previews
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
GuideImageView()
}
}
I want to make button to go to next page
button seems doesn't work
The reason for this not working is the type mismatch in your models id and the selection var.
Detail:
TabView(selection: $currentPage){
ForEach(guidelists){i in
these two lines tell the compiler that the id for every element is of type UUID (because GuideList is identifieable and id is of type UUID. Thats fine for itself, but TabView has a selection var of type Int (currentPage is an Int) so it is not working. So changing one of both types to equal the other will solve the problem.
easy example:
Change your code to:
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id: Int
let image: String
let explain: String
}
let guidelists = [
GuideList(id: 0, image: "image1",explain: "explain1."),
GuideList(id: 1, image: "image2",explain: "explain2." ),
GuideList(id: 2, image: "image3",explain: "explain3." )
]

How to link sing in to new page

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.

SwiftUI: Button in Form

I am creating a Form in SwiftUi with a section that is including a flexible number of instruction.
Next to the last instruction TextField, I am showing a "+"-Button that is extending the instructions array with a new member:
var body: some View {
NavigationView {
Form {
...
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel.recipe.instructions)
}
...
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
if(index == instructions.count-1) {
addInstructionButton
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}
Now the problem is, that the button click-area is not limited to the picture but to the whole last row. Precisely the part just around the textField, meaning if I click in it, I can edit the text, but if I click on the border somewhere, a new entry is added.
I assume that this is specific to Form {} (or also List{}), since it does not happen if I use a Button next to a text field in a "normal" set-up.
Is there something wrong with my code? Is this an expected behaviour?
I am not sure why border is getting tappable, but as a workaround I used plainButtonStyle and that seems to fix this issue, and keeps functionality intact .
struct TestView: View {
#State private var endAmount: CGFloat = 0
#State private var recipeViewModel = ["abc","Deef"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel)
}
}
}
}
}
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
Spacer()
if(index == instructions.count-1) {
addInstructionButton
.buttonStyle(PlainButtonStyle())
.foregroundColor(.blue)
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}

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: navigationBarItems missing on first render

I'm seeing a strange issue that I was able to reproduce with a small sample. If you have a detail view that has navigationBarItems set, and that detail is the second view pushed on a navigation stack, the items do not show up when you get to the detail page. Here is the sample:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: MiddleTestView()) {
Text("Push View")
}
}
}
}
}
struct MiddleTestView: View {
var body: some View {
VStack {
NavigationLink(destination: TestView()) {
Text("Push Another View")
}
}
}
}
struct TestView: View {
var body: some View {
VStack {
Text("Testing 1, 2, 3")
}
.navigationBarItems(leading: Button("Test") { print("pressed") })
}
}
If anything causes the TestView to re-render, then the button will show, for instance if the TestView does this:
struct TestView: View {
#State var hasChanges = false
var body: some View {
VStack {
Text("Testing 1, 2, 3")
Button("Toggle") { hasChanges = !hasChanges }
}
.navigationBarItems(leading: Button(hasChanges ? "Test1" : "Test2") { print("pressed") })
}
}
Then pressing the "Toggle" button once will cause the navigationBarItems to appear, and they will stay there until the view is dismissed. Additionally, if the TestView is shown first, instead of the MiddleTestView, then there is no problem with the navigationBarItems. I cannot see any reason for this behavior, it seems like a pretty glaring bug that makes working with navigation stacks in SwiftUI fundamentally broken, unless I'm missing something. Does anyone have any insight into what is going on here or how to get around it?

Resources