Data loss on View Class, when keyboard appears - ios

When ever I click on textField and key board appears, my list of data i.e coming from API is vanashing,
import SwiftUI
import ExytePopupView
struct Wallet: View {
#State private var searchText = ""
#State private var showCancelButton: Bool = false
#ObservedObject var walltetVM = ShopViewModel()
#State var showShopDetails: Bool = false
#State var openShowDetails: Bool = false
#State var selectedShopId:String = ""
#State var selectedCouponDetail: CouponModel?
var body: some View {
NavigationView {
VStack {
// Search view
VStack{
HStack {
Button {
} label: {
Image(systemName: "magnifyingglass")
.foregroundColor(AppColors.semiBlue)
.frame(width: 20, height: 20, alignment: .center)
.padding()
}
ZStack(alignment: .leading) {
if searchText.isEmpty {
Text(MaggnetLocalizedString(key: "Restaurant, Beauty shop...", comment: ""))
.foregroundColor(AppColors.blackWhite)
.font(Font(AppFont.lightFont(lightFontWithSize: 15)))
}
TextField("", text: $searchText)
.font(Font(AppFont.regularFont(regularFontWithSize: 15)))
}
.foregroundColor(AppColors.blackWhite)
.padding(.horizontal,10)
.padding(.leading,-15)
Divider()
Button {
} label: {
HStack {
Image("places")
}
.padding(.horizontal,20)
.padding(.leading,-12)
}
}
.frame(height: 45)
.background(AppColors.fadeBackground)
.clipShape(Capsule())
}
.padding(.horizontal)
.padding(.vertical,10)
ScrollView(.vertical) {
VStack{
NavigationLink(destination:ShopDetail(shopId:self.selectedShopId)
.environmentObject(walltetVM),
isActive: $openShowDetails) {
EmptyView()
}.hidden()
Points()
ForEach(0..<walltetVM.finalsCouponList.count,id: \.self){
index in
VStack{
// SHOP LIST HEADERS
HStack{
Text(walltetVM.finalsCouponList[index].name)
.multilineTextAlignment(.leading)
.font(Font(AppFont.mediumFont(mediumFontWithSize: 15)))
.foregroundColor(AppColors.blackWhite)
.padding(.horizontal,10)
Spacer()
Button {
} label: {
Text(MaggnetLocalizedString(key: "viewAll", comment: ""))
.font(Font(AppFont.regularFont(regularFontWithSize: 12)))
.foregroundColor(AppColors.blackWhite)
.padding()
.frame(height: 27, alignment: .center)
.background(AppColors.fadeBackground)
.cornerRadius(8)
}
}
.padding()
// MAIN SHOP LIST
VStack{
ScrollView(.horizontal,showsIndicators: false){
HStack{
ForEach(0..<walltetVM.finalsCouponList[index].couopons.count,id: \.self){
indeX in
Shops(coupons: walltetVM.finalsCouponList[index].couopons[indeX])
.onTapGesture {
selectedShopId = walltetVM.finalsCouponList[index].couopons[indeX].businessId?.description ?? ""
print(selectedShopId)
selectedCouponDetail = walltetVM.finalsCouponList[index].couopons[indeX]
showShopDetails = true
}
}
}
.padding(.horizontal)
}
}
.padding(.top,-5)
}
.padding(.top,-5)
}
}
}
.blur(radius: showShopDetails ? 3 : 0)
.popup(isPresented: $showShopDetails, autohideIn: 15, dismissCallback: {
showShopDetails = false
}) {
ShopDetailPopUp(couponDeatil: self.selectedCouponDetail)
.frame(width: 300, height: 400)
}
.navigationBarTitle(Text("Wallet"),displayMode: .inline)
.navigationBarItems(trailing: HStack{
Button {
} label: {
Image("wishIcon").frame(width: 20, height: 20, alignment: .center)
}
Button {
} label: {
Image("notifIcon").frame(width: 20, height: 20, alignment: .center)
}
})
.resignKeyboardOnDragGesture()
}
.onAppear(){
walltetVM.getWallets()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.openShopDetails))
{ obj in
showShopDetails = false
openShowDetails.toggle()
}
.environment(\.layoutDirection,Preferences.chooseLanguage == AppConstants.arabic ? .rightToLeft : .leftToRight)
}
}
}
struct Wallet_Previews: PreviewProvider {
static var previews: some View {
Wallet()
}
}
bannerList is marked as #Published, API call working fine, but in same View class I have one search text field , when I tap on it all the data I was rendering from API get lost and disappears from list.

You are holding a reference to your view model using ObservedObject:
#ObservedObject var walltetVM = ShopViewModel()
However, because you are creating the view model within the view, the view might tear down and recreate this at any moment, which might be why you are losing your data periodically.
If you use StateObject, this ensures that SwiftUI will retain the object for as long as the view lives, so the object won't be recreated.
#StateObject var walltetVM = ShopViewModel()
Alternatively, you could create the view model outside of the view and inject it into the view and keep using ObservedObject (but you'll still need to make sure the object lives as long as the view).

I found something working but still not optimised solution
instead of calling it on onAppear, I called API function in init method.
init(){
walltetVM.getWallets()
}

Related

Can't refresh view after changing published variable

I have three files: ContentView (main file), HomeView, ConfigView
I wish I could change one string that is located in HomeView by pressing a button in ConfigView, but I can't do it.
Edit: I realized the problem is because I'm storing objects inside the DadosTimes class. I tried to store a simple string and it worked. How can I make it work even using an object?
ContentView file:
import SwiftUI
extension View {
func inExpandingRectangle() -> some View {
ZStack {
Rectangle()
.fill(Color.clear)
self
}
}
}
//Here i have a declaration for a custom button
class Equipe {
var nome: String
var pontos: Int
var vitorias: Int
init(nome: String) {
self.nome = nome
self.pontos = 0
self.vitorias = 0
}
func addPontos(_qtd: Int){
self.pontos += _qtd
}
}
struct ContentView: View {
#EnvironmentObject var InfoJogo: DadosTimes
var body: some View {
TabView{
HomeView()
.tabItem({
Image(systemName: "house")
Text("Placar")
})
ConfigView()
.tabItem({
Image(systemName: "gear")
Text("Configurações")
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class DadosTimes: ObservableObject{
#Published var time1 = Equipe(nome: "Nós")
#Published var time2 = Equipe(nome: "Eles")
}
HomeView file:
import SwiftUI
struct HomeView: View {
#EnvironmentObject var InfoJogo: DadosTimes
var body: some View {
ZStack{
Color("FundoVerde")
.ignoresSafeArea()
VStack {
HStack{
VStack(spacing: 0){
TextField("", text: $InfoJogo.time1.nome)
.foregroundColor(Color.black)
.font(.system(size: 50))
Text(String(InfoJogo.time1.pontos))
.foregroundColor(Color.black)
.font(.system(size: 85))
Text(String(InfoJogo.time1.vitorias) + " Vitórias")
.foregroundColor(Color(.darkGray))
.font(.system(size: 17))
BotaoPrimario(title: "+", size: 50, action: {
InfoJogo.time1.addPontos(_qtd: 1)
verificaGanhador()
})
}
.inExpandingRectangle()
.fixedSize(horizontal: false, vertical: true)
VStack(spacing: 0){
Text(InfoJogo.time2.nome)
.foregroundColor(Color.black)
.font(.system(size: 50))
Text(String(InfoJogo.time2.pontos))
.foregroundColor(Color.black)
.font(.system(size: 85))
Text(String(InfoJogo.time2.vitorias) + " Vitórias")
.foregroundColor(Color(.darkGray))
.font(.system(size: 17))
BotaoPrimario(title: "+", size: 50, action: {
InfoJogo.time2.addPontos(_qtd: 1)
verificaGanhador()
})
}
.inExpandingRectangle()
.fixedSize(horizontal: false, vertical: true)
}
.padding(.vertical, 50)
.frame(maxWidth: .infinity)
.background(Rectangle()
.foregroundColor(.white)
.cornerRadius(15)
.shadow(radius: 15)
)
}
.padding(.horizontal, 20)
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
ConfigView file
import SwiftUI
struct ConfigView: View {
#EnvironmentObject var InfoJogo: DadosTimes
var body: some View {
ZStack{
Color("FundoVerde")
.ignoresSafeArea()
VStack{
Button("Mudar Nome"){
InfoJogo.time1.nome = "oiii"
}
TextField("", text: $InfoJogo.time1.nome)
}
}
}
}
struct ConfigView_Previews: PreviewProvider {
static var previews: some View {
ConfigView()
}
}
I have already logged in console the InfoJogo.time1.nome variable and it changes in the memory. It's not updating on the screen. What should I do? I've already looked for help everywhere but I couldn't find the solution.
Sorry if it's a basic question, but I've started learning swift yesterday ;)
#Paulw11 provided a perfect description why the change doesn't trigger an update.
What you can do is trigger the update manually with objectWillChange.send().
In your button action do:
BotaoPrimario(title: "+", size: 50, action: {
infoJogo.objectWillChange.send() // here
infoJogo.time2.addPontos(_qtd: 1)
verificaGanhador()
})

Data is not saved or load because of i am call the data from another pages

Model
I have different variables in Travel.
import Foundation
struct Travel: Identifiable, Codable {
var id = UUID()
var name: String
var date = Date()
var location: String
var isFav: Bool
var score: Float
var comment: String
}
View model
I load and save data with UserDefaults. Always its work but in this model not.
import Foundation
class TravelViewModel: ObservableObject {
#Published var travelList = [Travel] ()
#Published var travelled = 0
init(){
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "travelList"),
let savedTravels = try? JSONDecoder().decode([Travel].self, from: data) else { travelList = []; return }
travelList = savedTravels
}
func save() {
do {
let data = try JSONEncoder().encode(travelList)
UserDefaults.standard.set(data, forKey: "travelList")
} catch {
print(error)
}
}
}
Adding Item View
I have addItems func and use this func in addItem button.
import SwiftUI
struct AddTravelView: View {
#StateObject var VM = TravelViewModel()
#State var name = ""
#State var location = ""
#State var isFav = false
#State var score = 0.00
#State var comment = ""
var body: some View {
VStack {
ZStack {
Rectangle()
.fill(.black.opacity(0.2))
.cornerRadius(20)
.frame(width: 350, height: 350)
VStack{
HStack {
Text("Name:")
.font(.system(size: 16 , weight: .medium))
TextField("Type..", text: $name)
}
HStack {
Text("Location:")
.font(.system(size: 16 , weight: .medium))
TextField("Type..", text: $location)
}
HStack {
Text("Score: \(Int(score))")
.font(.system(size: 16 , weight: .medium))
Slider(value: $score, in: 0...10, step: 1)
}
Spacer()
ZStack(alignment: .topLeading) {
Rectangle()
.fill(.white)
.cornerRadius(20)
.frame(height: 200)
VStack(alignment: .leading) {
TextField("Comment...", text: $comment, axis: .vertical)
}.padding()
}
}
.padding()
.frame(width: 300, height: 200)
}
Button {
addTravel()
} label: {
ZStack{
Rectangle()
.fill(.black.opacity(0.2))
.cornerRadius(20)
.frame(width: 350 , height: 100)
Text("ADD TRAVEL")
.font(.system(size: 25, weight: .medium, design: .monospaced))
.foregroundColor(.black)
}.padding()
}
}
}
func addTravel(){
VM.travelList.append(Travel(name: name, location: location, isFav: isFav, score: Float(score), comment: comment))
}
}
struct AddTravelView_Previews: PreviewProvider {
static var previews: some View {
AddTravelView()
}
}
Recent Adds view
In this page i wanna see Items i add before
import SwiftUI
struct RecentTravels: View {
#StateObject var VM = TravelViewModel()
var body: some View {
VStack {
ForEach(VM.travelList) {Travel in
HStack{
Image(systemName: "questionmark")
.frame(width: 50, height: 50)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 2)
}
VStack(alignment: .leading) {
Text(Travel.name)
.font(.subheadline)
.bold()
.lineLimit(1)
Text("\(Travel.date)")
.font(.footnote)
.opacity(0.9)
.lineLimit(1)
}
Spacer()
VStack {
Image(systemName: "heart")
Spacer()
Text("\(Travel.score)")
}
.frame(height: 50)
.font(.system(size: 22))
}
}
}
}
}
struct RecentTravels_Previews: PreviewProvider {
static var previews: some View {
RecentTravels()
}
}
And ContentView
and calling those 2 views in ContentView.
import SwiftUI
struct ContentView: View {
#StateObject var VM = TravelViewModel()
var body: some View {
VStack {
AddTravelView()
RecentTravels()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When i write the all code in only ContentView is work but when i call another pages its not work. Usually it was work when i presss add item button and restart app but now its nothing. Its not work even restart the app.
The problem you have is that you have multiple VM, that have no relations to each other. You must not have more than one source of truth
in #StateObject var VM = TravelViewModel().
Keep the one you have in ContentView,
and pass it to the other view like this:
VStack {....}.environmentObject(VM).
In your AddTravelView and RecentTravels ,
add #EnvironmentObject var VM: TravelViewModel instead of #StateObject var VM = TravelViewModel().
Have a look at this link, it gives you some good examples of how to manage data in your app:
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

How to hide TabView in NavigationLink?

First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.

How to add button next to search bar when selected in SwiftUI (or UIKit if it's not possible in SwiftUI)?

How can I add a button next to the searchbar when it is currently selected in SwiftUI? An example of what I am talking about is below:
This is the code I have currently:
struct ContentView: View {
#State private var searchText = ""
var body: some View {
NavigationView {
VStack {
Text("Hello")
}
.searchable(text: $searchText)
.navigationTitle("Hello")
}
}
}
If this is not possible to do in SwiftUI, is there a way I can accomplish this by utilizing UIKit in my SwiftUI view?
You can achieve this very easily with #FocusState. here is complete code, you can change it according to your requirements.
struct CustomSearchBar: View {
#Binding var text: String
#State private var isEditing = false
#FocusState private var isSearchFocused: Bool
var body: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.white)
.padding([.leading])
TextField("Search ...", text: $text)
.padding([.trailing,. top, .bottom],7)
.foregroundColor(.white)
.opacity(isEditing ? 1 : 0.7)
.focused($isSearchFocused)
if isSearchFocused {
Button(action: {
// do you option event
}) {
Image("option")
.resizable()
.frame(width: 25, height: 25)
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
}
Button(action: {
// do you cancle event
isSearchFocused = false
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
}
.padding()
.background(.gray.opacity(0.3))
.onChange(of: isSearchFocused) { newValue in
text = ""
}
}
}

Implementing custom sidebar across all views - SwiftUI

OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}

Resources