I am building an EV Station app on IOS, which provides users with a map and pins representing EV Charging Stations. I have a NavigationLink with destination: another view that applies filters to the map results. And label: A custom built button. The problem is that when I switch apps and returns again to my app the NavigationLink is not working anymore, not until I switch apps again and return to my app a second time, only then the NavigationLink works again.
This behavior is repeated when I repeat the steps described above.
Below you can see my MainView which contains the above mentioned NavigationLink. you can also find and clone the whole project on github.
import SwiftUI
import MapKit
import CoreLocationUI
import Foundation
import AlertToast
struct MainView: View {
#StateObject var locationController = LocationController()
#StateObject var networkController = NetworkController()
#State public var showingSheet = false
#State public var dismissSheet = false
#State private var showToast = false
#State public var applyFilters = false
#State public var type1 = false
#State public var type2 = false
#State public var csstype1 = false
#State public var csstype2 = false
#State public var chademo = false
#State public var schuko = false
#State public var tesla = false
#State public var dc = false
#State public var ac1 = false
#State public var ac2 = false
#State public var ac2s = false
#State public var ac3 = false
#State public var format: Int = 3
#State public var status: Int = 7
var map: MapView {
MapView(
locationController: locationController,
networkController: networkController,
showingSheet: $showingSheet,
dismissSheet: $dismissSheet,
didSelect: {_ in}
)
}
var body: some View {
NavigationView {
ZStack(alignment: .bottom) {
map.edgesIgnoringSafeArea(.bottom)
LocationButton(.currentLocation) {
print("tapped")
locationController.requestAllowOnceLocationPermission()
}
.foregroundColor(.white)
.cornerRadius(8)
.labelStyle(.iconOnly)
.tint(Color("LightBlue"))
.padding(.bottom, 100 )
.padding(.leading, 330 )
NavigationLink(
destination: FiltersView(
applyFilters: $applyFilters,
type1: $type1,
type2: $type2,
csstype1: $csstype1,
csstype2: $csstype2,
chademo: $chademo,
schuko: $schuko,
tesla: $tesla,
dc: $dc,
ac1: $ac1,
ac2: $ac2,
ac2s: $ac2s,
ac3: $ac3,
format: $format,
status: $status),
label: {
ZStack {
RoundedRectangle(cornerRadius: 8, style: .circular)
.fill(Color("LightBlue"))
HStack {
SwiftUI.Image("Filters")
.resizable()
.scaledToFit()
.padding(10)
Text("Φίλτρα")
.foregroundColor(.white)
.font(.system(size: 15, weight: .regular))
.padding(.trailing, 5)
}
}.frame(minWidth: 100, maxWidth: 110, minHeight: 40, maxHeight: 40)
})
.padding(.bottom, 660 )
.padding(.leading, 250 )
}
//.preferredColorScheme(.dark)
.background(Color("DeepBlue"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
SwiftUI.Image("Logo")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
}
}
.sheet(item: $networkController.activeLocation, onDismiss: onSheetDismiss) { item in
BottomSheetView(data: item)
.onDisappear() {
print("dissapear")
}
}
.onAppear {
print("Active View: Main")
if applyFilters {
networkController.applyFilters = .constant(true)
NetworkVariables.FilterOptions.type1 = type1
NetworkVariables.FilterOptions.type2 = type2
NetworkVariables.FilterOptions.csstype1 = csstype1
NetworkVariables.FilterOptions.csstype2 = csstype2
NetworkVariables.FilterOptions.chademo = chademo
NetworkVariables.FilterOptions.schuko = schuko
NetworkVariables.FilterOptions.tesla = tesla
NetworkVariables.FilterOptions.dc = dc
NetworkVariables.FilterOptions.ac1 = ac1
NetworkVariables.FilterOptions.ac2 = ac2
NetworkVariables.FilterOptions.ac2s = ac2s
NetworkVariables.FilterOptions.ac3 = ac3
NetworkVariables.FilterOptions.format = format
NetworkVariables.FilterOptions.status = status
Task {
await networkController.getToken()
showToast.toggle()
}
applyFilters = false
}
}
}
.toast(isPresenting: $showToast){
AlertToast(type: .regular, title: "Βρέθηκαν \(networkController.mapLocations.count) σημεία")
}
}
func onSheetDismiss() {
print("sheet dismissed")
map.dismissSheet = true
}
}
I found what the problem was. As I had read before, this kind of problem usually happens because the app is running on an emulator. So while I was having this problem on physical devices, I realised that the physical devices I was testing were running the app on Testflight which is kind of a sandbox as far as I've understood it. So, the problem was caused because the app was running on testflight, when I installed the app directly to an iOS device through USB, it worked just fine.
Thanks for your replies.
Related
I am trying to follow the guidance in a WWDC video to use a #State struct to configure and present a child view. I would expect the struct to be able to present the view, however the config.show boolean value does not get updated when set by the button.
The code below has two buttons, each toggling a different boolean to show an overlay. Toggling showWelcome shows the overlay but toggling config.show does nothing. This seems to be working as intended, I just don't understand why SwiftUI behaves this way. Can someone explain why it's not functioning like I expect, and/or suggest a workaround?
https://developer.apple.com/videos/play/wwdc2020/10040/ # 5:14
struct InformationOverlayConfig {
#State var show = false
#State var title: String?
}
struct InformationOverlay: View {
#Binding var config: InformationOverlayConfig
var body: some View {
if config.title != nil {
Text(config.title!)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
}
}
struct TestView: View {
#State private var configWelcome = InformationOverlayConfig(title: "Title is here")
#State private var showWelcome = false
var body: some View {
VStack {
Text("hello world")
Spacer()
Button("Toggle struct parameter", action: {
configWelcome.show.toggle()
})
Button("Toggle boolean state", action: {
showWelcome.toggle()
})
}
.overlay(
VStack {
InformationOverlay(config: $configWelcome).opacity(configWelcome.show ? 1 : 0)
InformationOverlay(config: $configWelcome).opacity(showWelcome ? 1 : 0)
})
}
You "Config" is not a View. State variables only go in Views.
Also, do not use force unwrapping for config.title. Optional binding or map are the non-redundant solutions.
Lastly, there is no need for config to be a Binding if it actually functions as a constant for a particular view.
struct InformationOverlay: View {
struct Config {
var show = false
var title: String?
}
let config: Config
var body: some View {
VStack {
if let title = config.title {
Text(title)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
// or
config.title.map {
Text($0)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
}
}
}
struct TestView: View {
#State private var configWelcome = InformationOverlay.Config(title: "Title is here")
#State private var showWelcome = false
var body: some View {
VStack {
Text("hello world")
Spacer()
Button("Toggle struct parameter") {
configWelcome.show.toggle()
}
Button("Toggle boolean state") {
showWelcome.toggle()
}
}
.overlay(
VStack {
InformationOverlay(config: configWelcome).opacity(configWelcome.show ? 1 : 0)
InformationOverlay(config: configWelcome).opacity(showWelcome ? 1 : 0)
}
)
}
}
I am sorry im not sure that the title makes sense but if u read, im sure u will understand my problem. I have declared a variable #State var timetext: Int32 in the file CreatingWorkout, with a textfield TextField("5000, 100 etc", value: $timetext, formatter: NumberFormatter()) When i go to the createWorkoutView file and try to present it with the sheet, it wants me to give a value to timetext. However, when i provide a value with the textfield it stays constantly value given when calling with the sheet. I will attach a video here for you to see.
CreatingWorkout.swift :
struct CreatingWorkout: View {
#State var workoutTitle: String
#State var desc: String
#State var timetext: Int32
#State private var iconColor = Color.black
#State var displayWorkout: String = ""
#Environment(\.dismiss) var dismiss
#Environment(\.managedObjectContext) private var viewContext
private func saveWorkout() {
do {
let workout = Workout(context: viewContext)
workout.title = workoutTitle
workout.time = timetext
workout.icon = displayWorkout
workout.descriptionn = desc
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
CreateWorkout.swift :
import SwiftUI
struct CreateWorkoutView: View {
#State private var showingCreateWorkout = false
#State var timetext: Int32 = 0
var body: some View {
NavigationView {
VStack {
VStack(alignment: .center, spacing: 20) {
Text("Fitzy")
.font(.largeTitle)
.fontWeight(.bold)
Text("Create your first Workout")
.font(.title3)
.foregroundColor(.gray)
Button {
showingCreateWorkout.toggle()
} label: {
Image(systemName: "plus")
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(Color("AccentColor"))
.frame(width: 50, height: 50)
)
}.sheet(isPresented: $showingCreateWorkout) {
CreatingWorkout(workoutTitle: "", desc: "", timetext: timetext)
}
}
}.navigationTitle("Create Workout")
}
}
}
struct CreateWorkoutView_Previews: PreviewProvider {
static var previews: some View {
CreateWorkoutView()
}
}
https://drive.google.com/file/d/1qQDQmap5bMz9LxzibV98epHW5urqUtZ7/view?usp=sharing
as mentioned in the comments, you need a #State property to pass the value
that you type in your TextField to the sheet with CreatingWorkout. Try something like this:
struct CreatingWorkout: View {
#State var workoutTitle: String
#State var desc: String
#State var timetext: Int32
// ....
var body: some View {
Text("\(timetext)")
}
}
struct CreateWorkout: View {
#State var showingCreateWorkout = false
#State var timetext: Int32 = 0 // <-- here
var body: some View {
VStack {
TextField("type a number", value: $timetext, format: .number).border(.red) // <-- here
Button {
showingCreateWorkout.toggle()
} label: {
Image(systemName: "plus").font(.largeTitle).foregroundColor(.red).padding()
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(Color("AccentColor"))
.frame(width: 50, height: 50)
)
}.sheet(isPresented: $showingCreateWorkout) {
CreatingWorkout(workoutTitle: "", desc: "", timetext: timetext) // <-- here
}
}
}
}
I tried looking for an answer to this problem, but all of the solutions I found either were with UIKit, or needed me to add something to the AppDelegate/SceneDelegate. Since I'm using SwiftUI and Xcode 12.1 (that doesn't have AppDelegate and SceneDelegate anymore), I'm here asking this question:
How can I make this SearchBar disappear when user scrolls down (and leave it hidden until he/she scrolls down)?
Right now it works fine (even though I don't really like the animation of the cancel Button), and it look like this.
I have Search.swift like this:
import SwiftUI
struct Search: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search ...", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
Then in my Content View I have this:
import SwiftUI
struct ContentView: View {
#ObservedObject var dm : DataManager
#State var searchText = ""
#State private var isEditing = false
var body: some View {
NavigationView {
VStack {
Search(text: $searchText)
.padding(.horizontal, 10)
Form {
ForEach(dm.storageValues.filter({ searchText.isEmpty ? true : $0.valueName.contains(searchText) })) { data in
Text(data.valueName)
}
}
.navigationBarTitle("Example")
}
}
}
}
I don't know if they can be useful, but the DataManager is like this:
import SwiftUI
import Combine
class DataManager : Equatable, Identifiable, ObservableObject {
static let shared = DataManager()
#Published var storageValues : [ValueModel] = []
typealias StorageValues = [ValueModel]
//The rest of the code
}
With the ValueModel that is like this:
import SwiftUI
import Combine
class ValueModel : Codable, Identifiable, Equatable, ObservableObject, Comparable {
var id = UUID()
var valueName : String
var notes : String?
var expires : Date?
init(valueName: String, notes: String?, expires: Date?) {
self.valueName = valueName
self.notes = notes
self.expires = expires
}
}
For the SearchBar I followed this guide
Thanks to everyone who will help me!
I have a textfield which is supposed to log the units of a food product someone has eaten, which is then used to calculate the total number of calories, protein, etc. that the user consumed. But when the value is entered on the textfield, the units variable isn't updated. How can I fix this?
This is my code:
#State var selectFood = 0
#State var units = 0
#State var quantity = 1.0
#State var caloriesInput = 0.0
#State var proteinInput = 0.0
#State var carbsInput = 0.0
#State var fatsInput = 0.0
var body: some View {
VStack {
Group {
Picker(selection: $selectFood, label: Text("What did you eat?")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white))
{
ForEach(database.productList.indices, id: \.self) { i in
Text(database.productList[i].name)
}
}
.pickerStyle(MenuPickerStyle())
Spacer(minLength: 25)
Text("How much did you have?")
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(alignment: .leading)
//Textfield not working.
TextField("Units", value: $units, formatter: NumberFormatter())
.padding(10)
.background(Color("Settings"))
.cornerRadius(10)
.foregroundColor(Color("Background"))
.keyboardType(.numberPad)
Button (action: {
self.quantity = ((database.productList[selectFood].weight) * Double(self.units)) / 100
caloriesInput = database.productList[selectFood].calories * quantity
proteinInput = database.productList[selectFood].protein * quantity
carbsInput = database.productList[selectFood].carbs * quantity
fatsInput = database.productList[selectFood].fats * quantity
UIApplication.shared.hideKeyboard()
}) {
ZStack {
Rectangle()
.frame(width: 90, height: 40, alignment: .center)
.background(Color(.black))
.opacity(0.20)
.cornerRadius(15)
;
Text("Enter")
.foregroundColor(.white)
.fontWeight(.bold)
}
}
}
}
}
This is an issue with NumberFormatter that has been going on for a while. If you remove the formatter it updates correctly.
This is a workaround. Sadly it requires 2 variables.
import SwiftUI
struct TFConnection: View {
#State var unitsD: Double = 0
#State var unitsS = ""
var body: some View {
VStack{
//value does not get extracted properly
TextField("units", text: Binding<String>(
get: { unitsS },
set: {
if let value = NumberFormatter().number(from: $0) {
print("valid value")
self.unitsD = value.doubleValue
}else{
unitsS = $0
//Remove the invalid character it is not flawless the user can move to in-between the string
unitsS.removeLast()
print(unitsS)
}
}))
Button("enter"){
print("enter action")
print(unitsD.description)
}
}
}
}
struct TFConnection_Previews: PreviewProvider {
static var previews: some View {
TFConnection()
}
}
https://github.com/ryanpeach/RoutinesAppiOS
A Picker will not move, the Edit Button won't click, and a Text Field won't enter more than 1 character. Return on keyboard doesn't work. Most of these are tied to bindings to the ObservedObject and should be able to directly edit it. I believe there is a common cause with CoreData hanging on object updates. The done button on the final player view hangs, but the skip button does not! That means it only happens when TaskData is updated. If you delete an Alarm in certain situations the app crashes too.
Here's a video of the app behavior so far. In the view right before the end nothing will click. At the final view it hangs when you click the checkmark.
https://www.icloud.com/photos/#0HMiYqZ08ZYoFu5BEQXET4gRA
I am seeking some tips on how to debug this error. I can't put a breakpoint on the picker "when something changes" and I similarly cant do it on the Text Field. Why would a text field only take one character and then stop? Why would the edit button not work? This is the only view in the app where these sub-views don't work, the rest of the app works fine.
Some relevant background information:
I'm using coredata. There are 3 classes: AlarmData for the Routines Page, which has a one2many relationship to TaskData for the TaskList Page, which has a one2many relationship to SubTaskData for the TaskPlayerView and TaskEditor pages, the ones I'm having trouble with.
No further relationships.
I'm doing a fetchrequest at the root view and then using #ObservedObject the rest of the way down the view hierarchy. I'm using mostly isActive and tag:selection NavigationLinks.
The relevant file:
struct TaskEditorView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var taskData: TaskData
#State var newSubTask: String = ""
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
var body: some View {
VStack {
TitleTextField(text: self.$taskData.name)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
TimePickerRelativeView(time: self.$taskData.duration)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
HStack {
Spacer().frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: DEFAULT_HEIGHT_SPACING)
ReturnTextField(
label: "New Subtask",
text: self.$newSubTask,
onCommit: self.addSubTask
)
Button(action: {
self.addSubTask()
}) {
Image(systemName: "plus")
.frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: 30)
}
Spacer().frame(width: DEFAULT_HEIGHT_SPACING)
}
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
Text("Subtasks:")
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
List {
ForEach(self.subTaskDataList, id: \.id) { sub_td in
Text(sub_td.name)
}
.onDelete(perform: self.delete)
.onMove(perform: self.move)
}
}
.navigationBarItems(trailing: EditButton())
}
...
}
It also doesn't like to be edited here (see FLAG comment):
struct TaskPlayerView: View {
#Environment(\.managedObjectContext) var managedObjectContext
var taskDataList: [TaskData] {
return self.alarmData.taskDataList
}
var taskData: TaskData {
return self.taskDataList[self.taskIdx]
}
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
#ObservedObject var alarmData: AlarmData
#State var taskIdx: Int = 0
// For the timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var isPlay: Bool = true
#State var done: Bool = false
#State var startTime: Date?
#State var lastTime: Date = Date()
#State var durationBeforePause: TimeInterval = 0
#State var durationSoFar: TimeInterval = 0
var body: some View {
VStack {
if !self.done {
...
HStack {
Spacer()
Button(action: {
withAnimation {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
// FLAG: It fails here where setting task data
self.taskData.lastDuration_ = self.durationSoFar
self.taskData.done = true
self.taskData.lastEdited = Date()
self.next()
}
}
}) {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
} else {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
.foregroundColor(Color.gray)
}
}
Spacer()
}
...
}
...
}
...
}