SwiftUI use a Binding to some enums - swiftui

When you press the empty square, you change it to different images. It's for a simple game I created.
Now what I want is to bind these values so that when I go to a different view and come back these images are still there and do not get reset to their original state. So when you have a xmark, you still have the xmark when you come back and so on.
How would I use a binding with these enums?
I know how to use a binding with a boolean or a string but not an enum.
Can someone point me into the right direction?
This is my code:
enum dader1 : String, Identifiable {
case vierkantje
case vinkje
case vraagteken
case kruisje
var id: String {
return self.rawValue }
}
enum dader2 : String, Identifiable {
case vierkantje
case vinkje
case vraagteken
case kruisje
var id: String {
return self.rawValue }
}
enum dader3 : String, Identifiable {
case vierkantje
case vinkje
case vraagteken
case kruisje
var id: String {
return self.rawValue }
}
#State var activeDader1 : dader1? = nil
#State var activeDader2 : dader2? = nil
#State var activeDader3 : dader3? = nil
var body: some View {
ZStack {
Image("Achtergrond")
.resizable()
.ignoresSafeArea()
VStack(alignment: .leading) {
ScrollView {
Text("Some Text")
.frame(width: 350)
.foregroundColor(.black)
.font(.headline)
ZStack {
Image("Resultaat")
.resizable()
.frame(width: 350, height: 250)
VStack {
HStack(spacing: 55){
switch self.activeDader1 {
case .vierkantje:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader1 = .vinkje
Dader1 = false
}
case .vinkje:
Image(systemName: "checkmark.square" )
.foregroundColor(.blue)
.font(.title)
.onTapGesture {
self.activeDader1 = .vraagteken
Dader1 = true
}
case .vraagteken:
Image(systemName: "questionmark.square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader1 = .kruisje
Dader1 = false
}
case .kruisje:
Image(systemName: "xmark.square" )
.foregroundColor(.red)
.font(.title)
.onTapGesture {
self.activeDader1 = .vierkantje
Dader1 = false
}
case .none:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader1 = .vinkje
Dader1 = false
}
}
switch self.activeDader2 {
case .vierkantje:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader2 = .vinkje
Dader2 = true
}
case .vinkje:
Image(systemName: "checkmark.square" )
.foregroundColor(.blue)
.font(.title)
.onTapGesture {
self.activeDader2 = .vraagteken
Dader2 = false
}
case .vraagteken:
Image(systemName: "questionmark.square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader2 = .kruisje
Dader2 = false
}
case .kruisje:
Image(systemName: "xmark.square" )
.foregroundColor(.red)
.font(.title)
.onTapGesture {
self.activeDader2 = .vierkantje
Dader2 = false
}
case .none:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader2 = .vinkje
Dader2 = true
}
}
switch self.activeDader3 {
case .vierkantje:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader3 = .vinkje
Dader3 = false
}
case .vinkje:
Image(systemName: "checkmark.square" )
.foregroundColor(.blue)
.font(.title)
.onTapGesture {
self.activeDader3 = .vraagteken
Dader3 = true
}
case .vraagteken:
Image(systemName: "questionmark.square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader3 = .kruisje
Dader3 = false
}
case .kruisje:
Image(systemName: "xmark.square" )
.foregroundColor(.red)
.font(.title)
.onTapGesture {
self.activeDader3 = .vierkantje
Dader3 = false
}
case .none:
Image(systemName: "square" )
.foregroundColor(.gray)
.font(.title)
.onTapGesture {
self.activeDader3 = .vinkje
Dader11 = false
}
}
}

The problem is you are using #State instead of #Binding.
The State of the view resets on appear, but #bindings are stored across the views. You should use like this:
#Binding var activeDader1: dader1
#Binding var activeDader2: dader2
#Binding var activeDader3: dader3

Related

resizable header in SwiftUI

I was trying to make resizable header but it breaks.
I am trying to clone twitter profile,
I think logic is right but can I know why this one is not working?
I made HStack and try to hide it but when I scroll back it can't come back.
Tried with GeometryReader
Please help me, thanks
import SwiftUI
struct ProfileView: View {
// change views
#State var isHide = false
#State private var selectionFilter: TweetFilterViewModel = .tweets
#Namespace var animation
var body: some View {
VStack(alignment: .leading) {
//hiding
if isHide == false {
headerView
actionButtons
userInfoDetails
}
tweetFilterBar
.padding(0)
ScrollView(showsIndicators: false) {
LazyVStack {
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < height && !isHide {
DispatchQueue.main.async {
withAnimation {
isHide = true
}
}
}
if yAxis > 0 && isHide {
DispatchQueue.main.async {
withAnimation {
isHide = false
}
}
}
return AnyView(
Text("")
.frame(width: 0, height: 0)
)
}
.frame(width: 0, height: 0)
ForEach(0...9, id: \.self) { _ in
TweetRowView()
}
}
}
Spacer()
}
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
extension ProfileView {
var headerView: some View {
ZStack(alignment: .bottomLeading) {
Color(.systemBlue)
.ignoresSafeArea()
VStack {
Button {
} label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 16)
.foregroundColor(.white)
.position(x: 30, y: 12)
}
}
Circle()
.frame(width: 72, height: 72)
.offset(x: 16, y: 24)
}.frame(height: 96)
}
var actionButtons: some View {
HStack(spacing: 12){
Spacer()
Image(systemName: "bell.badge")
.font(.title3)
.padding(6)
.overlay(Circle().stroke(Color.gray, lineWidth: 0.75))
Button {
} label: {
Text("Edit Profile")
.font(.subheadline).bold()
.accentColor(.black)
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray, lineWidth: 0.75))
}
}
.padding(.trailing)
}
var userInfoDetails: some View {
VStack(alignment: .leading) {
HStack(spacing: 4) {
Text("Heath Legdet")
.font(.title2).bold()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue))
}
.padding(.bottom, 2)
Text("#joker")
.font(.subheadline)
.foregroundColor(.gray)
Text("Your mom`s favorite villain")
.font(.subheadline)
.padding(.vertical)
HStack(spacing: 24) {
Label("Gothem.NY", systemImage: "mappin.and.ellipse")
Label("www.thejoker.com", systemImage: "link")
}
.font(.caption)
.foregroundColor(.gray)
HStack(spacing: 24) {
HStack {
Text("807")
.font(.subheadline)
.bold()
Text("following")
.font(.caption)
.foregroundColor(.gray)
}
HStack {
Text("200")
.font(.subheadline)
.bold()
Text("followers")
.font(.caption)
.foregroundColor(.gray)
}
}
.padding(.vertical)
}
.padding(.horizontal)
}
var tweetFilterBar: some View {
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectionFilter == item ? .semibold : .regular)
.foregroundColor(selectionFilter == item ? .black : .gray)
ZStack {
Capsule()
.fill(Color(.clear))
.frame(height: 3)
if selectionFilter == item {
Capsule()
.fill(Color(.systemBlue))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
}
}
}
.onTapGesture {
withAnimation(.easeInOut) {
self.selectionFilter = item
}
}
}
}
}
}
While the animation between show and hide is running, the GeometryReader is still calculating values – which lets the view jump between show and hide – and gridlock.
You can introduce a new #State var isInTransition = false that checks if a show/hide animation is in progress and check for that. You set it to true at the beginning of the animation and to false 0.5 secs later.
Also I believe the switch height is not exactly 1/2 of the screen size.
So add a new state var:
#State var isInTransition = false
and in GeometryReader add:
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < 350 && !isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = true
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
} else if yAxis > 0 && isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = false
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
}
return AnyView(
// displaying values for test purpose
Text("\(yAxis) - \(isInTransition ? "true" : "false")").foregroundColor(.red)
// .frame(width: 0, height: 0)
)
}

hiding the navigation bar while scrolling SwiftUI

Hello I am new to swiftUI and am making my first app, my current issue is that I am not sure if it is possible to hide my navigation bar while scrolling using the methods I got from a nav bar tutorial I found. I have tried to implement the UINavigationBar stuff but I don't think its at all compatable with what I have so far. I'm pretty sure if I wanted to go that route I would have to rewrite most of what I have to fit that format.
I was hoping that maybe I am missing a step when doing this so I don't have to rewrite what I have
here's my current code
import Foundation
import SwiftUI
struct ContentView: View{
#State var showMenu = false
#State var scoreUp = false
#State var commentP = false
#State var tags = false
#State var sauce = false
var body: some View{
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
let dragOpen = DragGesture()
.onEnded {
if $0.translation.width > 100 {
withAnimation {
self.showMenu = true
}
}
}
return NavigationView {
GeometryReader { geometry in
ZStack(alignment: .leading) {
/// this is the main view for the second page allowing most of the app features
MainView(showMenu: self.$showMenu, scoreUp: self.$scoreUp, commentP : self.$commentP,tags : self.$tags,sauce :self.$sauce)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
.gesture(dragOpen)
if self.showMenu {
HamburgerMenuView(showMenu: self.$showMenu)
.frame(width: geometry.size.width/2, height: geometry.size.height)
.transition(.move(edge: .leading))
.gesture(drag)
}
if self.scoreUp {
// TO DO : MAke this button score up a post
Image(systemName: "arrow.up")
.animation(.interpolatingSpring(stiffness: 50, damping: 1), value: 10)
}
}
}
.navigationBarItems(leading: (
Button(action: {
withAnimation {
self.showMenu.toggle()
}
}) {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
))
}
}
}
// this is the main view for both the content view and when the menu is displayed
struct MainView: View{
/// shows the hamburger menu when set to true
#Binding var showMenu : Bool
/// SCORES up posts when set to true
#Binding var scoreUp : Bool
/// allows the user to add a comment to the post if set to true
/// move the view up and give the user a text box
#Binding var commentP : Bool
/// allows the user to see the Tags on the image
#Binding var tags : Bool
/// allows the user to see the Source on the image
#Binding var sauce : Bool
var body: some View {
GeometryReader { geometry in
ZStack() {
Image("");
ScrollView {
VStack(spacing: 20) {
VStack(alignment: HorizontalAlignment.leading){
Text("Test")
.bold();
Image("Test")
.resizable()
.scaledToFit()
HStack() {
Button(action : {
withAnimation{
self.scoreUp = true
}
print("Score up")
}) {
Image(systemName: "arrow.up")
.padding(1)
};
Text("1025");
Button(action : {
withAnimation{
self.commentP = true
}
print("comment")
}) {
Image(systemName: "text.bubble")
.padding(1)
};
Button(action : {
withAnimation{
self.tags = true
}
print("Tags")
}) {
Image(systemName: "tag")
.padding(1)
};
Button(action : {
withAnimation{
self.sauce = true
}
print("sauce?")
}) {
Image(systemName: "eyes")
.padding(1)
};
}
Text("Test")
Image("Test")
.resizable()
.scaledToFit();
Text("Test")
Image("Test")
.resizable()
.scaledToFit()
}
Button(action: {
withAnimation {
self.showMenu = true
}
print("Open the side menu")
}) {
Text("Show Menu");
}
.hidden()
}
}
}
}
}
}
/// this is the hamburger menu itself
struct HamburgerMenuView : View {
#Binding var showMenu : Bool
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Profile")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "gear")
.foregroundColor(.gray)
.imageScale(.large)
Text("Settings")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Favorites")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Posts")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Comments")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Artists")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Tags")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
HStack {
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("More")
.foregroundColor(.gray)
.font(.headline)
.padding(10)
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(.all)
}
Spacer()
}
}
//// TO DO
//class underImageBar {
//
//
//HStack() {
//Button(action : {
// withAnimation{
// self.scoreUp = true
//}
// print("Score up")
// }) {
// Image(systemName: "arrow.up")
// .padding()
//
//
// };
//Button(action : {
// withAnimation{
// self.commentP = true
//}
// print("comment")
// }) {
// Image(systemName: "text.bubble")
// .padding()
//
//
// };
//Button(action : {
// withAnimation{
// self.tags = true
//}
// print("Tags")
// }) {
// Image(systemName: "tag")
// .padding()
// };
//Button(action : {
// withAnimation{
// self.sauce = true
// }
// print("sauce?")
// }) {
// Image(systemName: "eyes")
// .padding()
//
// };
// }
//}
/// this previews the code
struct menuView: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.toolbar {
}
}
}
}
this was supposed to be a gif stack overflow just made it a jpeg lol?
There is a view modifier for that.
.navigationBarHidden(true)
I hope this helps out.

ScrollViewReader is not working and building is pretty slow

I couldn't figure out why it is not working my ScrollViewReader below my code. Please help me figure out the issue. I want to pass my zolyric.number into scrollToIndex and that scrollToIndex will set the number that I want to scroll in my HymnLyrics(). Also, although I couldn't find the error, the building is pretty slow.
Here ZolaiTitles() is the same list just sorting the song titles algebraically and HymnLyrics() is the one to display in the canvas.
struct ZolaiTitles: View {
#AppStorage("scrollToIndex") var scrollToIndex: Int?
#EnvironmentObject var tappingSwitches: TapToggle
let zoLyrics: [Lyric] = LyricList.hymnLa.sorted { lhs, rhs in
return lhs.zoTitle < rhs.zoTitle
}
var body: some View {
ScrollView {
ForEach(zoLyrics, id: \.id) { zoLyric in
VStack {
Button(action: {
//if let lyricNum = String(zoLyric) {
scrollToIndex = zoLyric.number // zolyric.number is already an interger.
//}
self.tappingSwitches.isHymnTapped.toggle()
}, label: {
HStack {
Text(zoLyric.zoTitle)
.foregroundColor(Color("bTextColor"))
.lineLimit(1)
.minimumScaleFactor(0.5)
Spacer()
Text("\(zoLyric.number)")
.foregroundColor(Color("bTextColor"))
}
})
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding([.leading, .bottom, .trailing])
}
}
}
}
struct HymnLyrics: View {
#AppStorage("scrollToIndex") var scrollToIndex: Int = 1
var lyrics: [Lyric] = LyricList.hymnLa
#AppStorage("fontSizeIndex") var fontSizeIndex = Int("Medium") ?? 18
#AppStorage("fontIndex") var fontIndex: String = ""
#AppStorage("showHVNumbers") var showHVNumbers: Bool = true
var body: some View {
ScrollViewReader { proxy in
List(lyrics, id: \.id) { lyric in
VStack(alignment: .center, spacing: 0) {
Text("\(lyric.number)")
.padding()
.multilineTextAlignment(/*#START_MENU_TOKEN#*/.leading/*#END_MENU_TOKEN#*/)
.id(lyric.number)
VStack {
VStack {
Text(lyric.zoTitle)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.primary)
.autocapitalization(.allCharacters)
Text(lyric.engTitle)
.font(.title3)
.fontWeight(.medium)
.foregroundColor(.secondary)
.italic()
}
// Don't use Padding here!
}
.multilineTextAlignment(.center)
HStack {
Text(lyric.key)
.italic()
Spacer()
Text(lyric.musicStyle)
}
.foregroundColor(.blue)
.padding(.vertical)
}
//.id(lyric.number)
}
.onChange(of: scrollToIndex, perform: { value in
proxy.scrollTo(value, anchor: .top)
})
}
}
}

SwiftUI: Tapping the return key of keyboard erases all the form data

I have forms in my SwiftUI app. In some of them there is a problem relating to keyboard's return key. After editing the form, when I tap the return key to resign the keyboard it erases all the edited data in the form. I could not find any reasonable cause of this problem. I have many network calls in the app.
Here is the code of the login form:
import SwiftUI
struct FormView: View {
var size: CGSize
#State private var errorMessage: String = ""
#State private var isConnectionFailed: Bool = false
#State private var isLoginActive: Bool = false
#ObservedObject var viewModel = LoginViewModel()
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ZStack {
VStack {
VStack {
Image("Logo Registration")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 150)
Text("Welcome")
.bold()
.font(.system(size: 22))
.foregroundColor(Color("T1"))
.padding(.top)
Text("Sign in to continue")
.font(.system(size: 18))
.foregroundColor(Color("T1"))
.padding(.top, 8)
}
.padding()
.padding(.bottom)
VStack(spacing: 20) {
HStack {
Image("Call Us_Menu")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 35)
.padding(.leading, -10)
Divider()
.rotationEffect(Angle(degrees: 180))
.frame(height: 50)
Text("+88")
.foregroundColor(.gray)
.padding(.horizontal, 10)
Divider()
.rotationEffect(Angle(degrees: 180))
.frame(height: 50)
TextField("Mobile Number", text: self.$viewModel.mobileNumber)
}
.crTextFieldStyle(size: size, height: 50)
HStack {
Image("Password")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 35)
.padding(.leading, -10)
Divider()
.rotationEffect(Angle(degrees: 180))
.frame(height: 50)
SecureField("Password", text: self.$viewModel.password)
}
.crTextFieldStyle(size: size, height: 50)
}
HStack {
Text(self.errorMessage)
.font(.system(size: 12))
.foregroundColor(Color("T2"))
Spacer()
}
.frame(width: size.width/1.2)
HStack {
NavigationLink(
destination: ResetPasswordStepOneView()
.navigationBarHidden(isHidden: true)
) {
Text("Forgot Password?")
.foregroundColor(Color("T1"))
}
Spacer()
}
.padding(.bottom, 40)
.frame(width: size.width/1.2)
.font(.system(size: 14))
if self.viewModel.loginModel?.status == "ok" {
NavigationLink(
destination: ContentView()
.navigationBarHidden(isHidden: true)
.navigationBarBackButtonHidden(true),
isActive: $isLoginActive,
label: {
EmptyView()
}
)
}
Button(action: {
if self.viewModel.mobileNumber == "" || self.viewModel.password == "" {
self.errorMessage = "Mobile number or password is missing"
} else if Connectivity.isConnectedToInternet() {
self.viewModel.loading = true
self.errorMessage = ""
self.isLoginActive.toggle()
self.viewModel.fetchWithAF()
} else {
self.isConnectionFailed = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.isConnectionFailed = false
}
}
}) {
Text("SIGN IN")
}
.frame(width: size.width/1.2, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color("B7"), lineWidth: 1)
)
.foregroundColor(Color("T2"))
Text("Or")
.padding()
NavigationLink(
destination: RegistrationView()
.navigationBarHidden(isHidden: true)
) {
Text("CREATE NEW ACCOUNT")
.foregroundColor(Color("T2"))
.bold()
}
Spacer()
}
.frame(width: size.width)
.padding(.bottom, 170)
if self.isConnectionFailed {
ConnectivityError()
}
}
}
}
}
LoginViewModel:
import Foundation
import SwiftUI
import Alamofire
class LoginViewModel: ObservableObject{
#Published var loginModel: LoginModel?
#Published var loading: Bool = false
#Published var isError: Bool = false
#Published var mobileNumber: String = ""
#Published var password: String = ""
#Published var isActive = false
func fetchWithAF() {
let registrationReq = LoginReqModel(phone: "+88" + self.mobileNumber, password: self.password)
let url = AppConstant.signin
let headers: HTTPHeaders = [
"Content-Type": "application/json"
]
AF.request(URL.init(string: url)!, method: .post, parameters: registrationReq, encoder: JSONParameterEncoder.default, headers: headers).responseJSON { (response) in
switch response.result {
case .success(_):
DispatchQueue.main.async {
do {
self.loginModel = try JSONDecoder().decode(LoginModel.self, from: response.data!)
if self.loginModel?.payload?.count ?? 0 > 0 {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(self.loginModel) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: AppConstant.loginPayoad)
}
AppConstant.token = self.loginModel?.payload![0].accessToken ?? ""
}
self.loading = false
if self.loginModel?.status == "ok" {
self.isActive = true
UserDefaults.standard.set(self.mobileNumber, forKey: "Mobile")
UserDefaults.standard.set(self.password, forKey: "Password")
}
if self.loginModel?.status == "error" {
self.isError = true
}
} catch {
print("")
}
}
break
case .failure(let error):
print("working error \(error)")
break
}
}
}
}
Your viewModel is recreated every time the view reloads. Try changing it to:
#StateObject var viewModel = LoginViewModel()
StateObject (introduced in iOS 14) will ensure the object is only created once per view. ObservedObjects need to be owned by some parent view or class.

SwiftUI List inside ZStack inside NavigationView

I am building a screen with list and custom full screen background color. On the top level I have NavigationView. To paint background I am using ZStack. The problem is that when I wrap list in ZStack and scroll it, list items collapse with NavigationView. I can achieve the correct behavior only if the list is direct child of NavView and has no changed background color.
Code:
init() {
// Title text color
let navBarAppearance = UINavigationBar.appearance()
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
navBarAppearance.backgroundColor = .clear
// Clear list background color
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().backgroundColor = .clear
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().selectionStyle = .none
}
var body: some View {
NavigationView {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color("DarkShade"), Color("PrimaryDark")]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
List(self.viewModel.rooms.enumerated().map({ $0 }), id: \.element.id) { (index, room) in
ZStack {
RoomsItemView(room: room)
.onAppear(perform: {
let count = self.viewModel.rooms.count
if index == count - 1 {
self.viewModel.loadRooms(currentListSize: count)
}
})
NavigationLink(destination: GuestRoomView(room: room)) {
EmptyView()
}
}
}
}
.pullToRefresh(isShowing: self.$isRefreshing, onRefresh: self.onRefresh)
.navigationBarTitle("rooms_title")
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading:
Button(action: {
self.shouldShowSpotifyAuth = false
}) {
VStack {
Spacer()
HStack {
Image(systemName: "chevron.left")
.resizable()
.scaledToFit()
.frame(height: 20.0)
Spacer()
}
Spacer()
}
.frame(width: 40.0, height: 30.0)
},
trailing:
Button(action: {
print("create room")
}) {
VStack {
Spacer()
HStack {
Spacer()
Image(systemName: "plus")
.resizable()
.scaledToFit()
.frame(height: 20.0)
}
Spacer()
}
.frame(width: 40.0, height: 30.0)
}
)
}
.onAppear(perform: {
self.onRefresh()
})
.onReceive(viewModel.onRoomsUpdate) {
self.isRefreshing = false
}
.gesture(DragGesture().updating($dragOffset, body: { (value, state, transaction) in
if(value.startLocation.x < 20 && value.translation.width > 100) {
self.shouldShowSpotifyAuth = false
}
})
)
}
Expected result:
Expected result
Actual result:
Actual result