swiftui page blank after render - swiftui

I have a problem with a view. The view in question once entered in it, render the screen for a moment and then disappears. I Load data from firebase. At the hierarchical level it is the third view
VIEW A -> VIEW B -> VIEW C
if arrive in C from B, i've the problem, if arrive from A the problem its not present.
The problem is "self.lineup.fetchHomeTeam" after onAppear return empty
The data passed from ViewB To ViewC are correct
VIEW C (TeamsModuleView) -> Page with problem
struct TeamsModuleView: View {
#ObservedObject var lineup = LineupViewModel()
#EnvironmentObject var settings: UserSettings
var body: some View {
ScrollView(.vertical) {
Group {
VStack(spacing: 20, content: {
ForEach(lineup.lineupHome, id: \.self) { module in
HStack(alignment: .top, spacing: 10, content: {
ForEach(module.name, id: \.self) { name in
Group {
Spacer()
VStack(alignment: .center, spacing: 0, content: {
Spacer().frame(height: 20)
Image("home")
.resizable()
.frame(width: 30, height: 30)
Text(name)
.foregroundColor(Color.white)
.font(.system(size: 10))
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
Spacer().frame(height: 5)
})
Spacer()
}
}
})
}
ForEach(lineup.lineupAway, id: \.self) { module in
HStack(alignment: .top, spacing: 10, content: {
ForEach(module.name, id: \.self) { name in
Group {
Spacer()
VStack(alignment: .center, spacing: 0, content: {
Spacer().frame(height: 5)
Image("transfert")
.resizable()
.frame(width: 30, height: 30)
Text(name)
.foregroundColor(Color.white)
.font(.system(size: 10))
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
Spacer().frame(height: 20)
})
Spacer()
}
}
})
}
})
.background(
Image("field3")
.resizable()
.aspectRatio(contentMode: .fill)
).edgesIgnoringSafeArea(.all)
}
}.onAppear {
self.lineup.fetchHomeTeam(fixturesId: String(self.settings.fixtureId), teamId: String(self.settings.teamHomeId), team: self.settings.teamHome)
self.lineup.fetchAwayTeam(fixturesId: String(self.settings.fixtureId), teamId: String(self.settings.teamAwayId), team: self.settings.teamAway)
}.onDisappear {
print(self.lineup.lineupHome.isEmpty)
}
.navigationBarTitle("Formazione", displayMode: .inline) //Return true i dont why
}
}
struct TeamsModuleView_Previews: PreviewProvider {
static var previews: some View {
TeamsModuleView()
}
}
LineupViewModel
final class LineupViewModel: ObservableObject {
#Published var lineup = Lineup()
#Published var lineupHome = [LineupView]()
#Published var lineupAway = [LineupView]()
func fetchHomeTeam(fixturesId: String, teamId: String, team: String) {
Webservices().getLineUp(fixturesId: fixturesId, teamId: teamId, team: team) {
self.lineup = $0
var lineupModTemp = [LineupView]()
-
-
-
DispatchQueue.main.async {
self.lineupHome = lineupModTemp
}
}
}
func fetchAwayTeam(fixturesId: String, teamId: String, team: String) {
Webservices().getLineUp(fixturesId: fixturesId, teamId: teamId, team: team) {
self.lineup = $0
var lineupModTemp = [LineupView]()
-
-
-
DispatchQueue.main.async {
self.lineupAway = lineupModTemp
}
}
}
}
UserSettings(the real data are modify in View B onclik)
class UserSettings: ObservableObject {
#Published var teamHomeId = 505
#Published var teamAwayId = 518
#Published var teamHome = "Brescia"
#Published var teamAway = "Inter"
#Published var fixtureId = 232614
}

Related

SwiftUI ScrollView messing up picker selection behaviour

I'm building a WatchOS-app (SwiftUI) with multiple pickers, but as soon as I add them to a ScrollView I can no longer simply tap a picker to select it.
When I tap a picker the first picker on the screen gets selected and I have to tap once more to have the right picker selected.
Once I've double tapped the picker I can select other pickers just fine, but as soon as I tap outside to deselect all pickers I have to double tap again.
Sorry if the explanation is a bit fuzzy. This video shows the issue: Video
I'm new to both programming and Swift, so be gentle ;)
import SwiftUI
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentView: View {
let paceArray = Array(0...59)
let speedArray = Array(0...99)
#State private var globalSecondsPerKM: Double = 0
#State private var paceMKMHours: Int = 0
#State private var paceMKMMinutes: Int = 0
#State private var paceMKMSeconds: Int = 0
#State private var paceMMHours: Int = 0
#State private var paceMMMinutes: Int = 0
#State private var paceMMSeconds: Int = 0
#State private var speedKMHWhole: Int = 0
#State private var speedKMHDecimal: Int = 0
#FocusState private var paceMKMFocused: Bool
#FocusState private var paceMMFocused: Bool
#FocusState private var speedKMHFocused: Bool
var body: some View {
ScrollView {
VStack {
VStack {
Text("Pace per km")
.font(.headline)
HStack {
Picker(selection: $paceMKMHours, label: Text(""), content: {
ForEach(0..<speedArray.count, id: \.self) { index in
Text(String(format: "%02dh", speedArray[index])).tag(index)
}
})
.frame(width: 45)
Picker(selection: $paceMKMMinutes, label: Text(""), content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02dm", paceArray[index])).tag(index)
}
})
.frame(width: 45)
Picker(selection: $paceMKMSeconds, label: Text(""), content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02ds", paceArray[index])).tag(index)
}
})
.frame(width: 45)
}
.focused($paceMKMFocused)
.padding(.bottom, 5)
.frame(height: 35)
}
Divider()
VStack {
Text("Pace per mile")
.font(.headline)
HStack {
Picker(selection: $paceMMHours, label: Text(""), content: {
ForEach(0..<speedArray.count, id: \.self) { index in
Text(String(format: "%02dh", speedArray[index])).tag(index)
}
})
.frame(width: 45)
Picker(selection: $paceMMMinutes, label: Text(""), content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02dm", paceArray[index])).tag(index)
}
})
.frame(width: 45)
Picker(selection: $paceMMSeconds, label: Text(""), content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02ds", paceArray[index])).tag(index)
}
})
.frame(width: 45)
}
.focused($paceMMFocused)
.padding(.bottom, 5)
.frame(height: 35)
}
Divider()
VStack {
Text("Speed in km/h")
.font(.headline)
HStack {
Picker(selection: $speedKMHWhole, label: Text(""), content: {
ForEach(0..<speedArray.count, id: \.self) { index in
Text(String(format: "%02d", speedArray[index])).tag(index)
}
})
.frame(width: 45)
Picker(selection: $speedKMHDecimal, label: Text("")) {
ForEach(0..<speedArray.count, id: \.self) { index in
Text(String(format: ".%02d", speedArray[index])).tag(index)
}
}
.frame(width: 45)
}
.focused($speedKMHFocused)
.padding(.bottom, 5)
.frame(height: 35)
}
Divider()
}
}
.labelsHidden()
.font(.system(size: 13))
}
}
This looks like a SwiftUI bug. A possible workaround is setting up a tap gesture on the picker, which triggers a focus change. The initial animation is not perfect, but it looks fine after that.
import SwiftUI
#available(watchOSApplicationExtension 8.0, *)
struct ContentView: View {
let paceArray = Array(0...59)
#State private var paceMKMHours: Int?
#State private var paceMKMMinutes: Int?
#State private var paceMKMSeconds: Int?
#FocusState private var shouldFocusHours: Bool
#FocusState private var shouldFocusMinutes: Bool
#FocusState private var shouldFocusSeconds: Bool
var body: some View {
ScrollView {
VStack {
HStack {
Picker("hours", selection: $paceMKMHours, content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02dh", paceArray[index])).tag(index)
}
})
.onTapGesture {
shouldFocusHours = true
}
.focused($shouldFocusHours)
Picker("minutes", selection: $paceMKMMinutes, content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02dm", paceArray[index])).tag(index)
}
})
.onTapGesture {
shouldFocusMinutes = true
}
.focused($shouldFocusMinutes)
Picker("seconds", selection: $paceMKMSeconds, content: {
ForEach(0..<paceArray.count, id: \.self) { index in
Text(String(format: "%02ds", paceArray[index])).tag(index)
}
})
.onTapGesture {
shouldFocusSeconds = true
}
.focused($shouldFocusSeconds)
}
.padding(.bottom, 5)
.frame(height: 35)
}
}
.labelsHidden()
.font(.system(size: 13))
}
}

How to add button in custom View (ListRowView) without providing prior functionality, in Swift UI?

Code below is working perfectly but i have an issue, i don't want to provide functionality here, i just want to add button (dropDownButton) and provide the functionality when using this custom view.
struct DrawerItemListRowView: View {
#State var iconName: Icon
#State var text: String
#State var dropDownButton = Button(action: {}) {
Image(icon: .drawer)
}
var body: some View {
HStack(alignment: .center, spacing: 15) {
Image(icon: iconName)
Text(text)
.foregroundColor(.customLightBlack)
.font(.custom(ubuntu: .regular, style: .title2))
Spacer()
dropDownButton
.frame(width: 24, height: 24, alignment: .trailing)
}
.padding()
.listRowSeparator(.hidden)
.listRowBackground(Color.customBackground)
.background(Color.clear)
}
}
struct DrawerItemListRowView_Previews: PreviewProvider {
static var previews: some View {
Group {
DrawerItemListRowView(iconName: .mainCategory, text: "Shop by category")
DrawerItemListRowView(iconName: .paymentMethod, text: "Payment Methods")
}
.previewLayout(.sizeThatFits)
.background(.white)
} }
You need to pass the action, not the button, as of type ()->Void.
Check out this example:
struct DrawerItemListRowView: View {
let iconName: String
let text: String
let action: ()->Void // Pass the action, not the button
var body: some View {
HStack(alignment: .center, spacing: 15) {
Image(systemName: iconName)
Text(text)
.foregroundColor(.gray)
Spacer()
Button {
action() // Call the action
} label: {
Text(text)
.fixedSize()
}
.frame(width: 24, height: 24, alignment: .trailing)
}
.padding()
.listRowSeparator(.hidden)
.listRowBackground(Color.yellow)
.background(Color.clear)
}
}
struct Example: View {
var body: some View {
VStack {
DrawerItemListRowView(iconName: "house", text: "Shop by category") {
print("Bought")
}
DrawerItemListRowView(iconName: "minus", text: "Payment Methods") {
print("Paid")
}
}
.previewLayout(.sizeThatFits)
.background(.white)
}
}

Why the scrollview doesn't get updated with new data from array?

I'm trying to send and then display them in the scrollview realtime. But nothing shows up. How to solve it? So, basically when the user types the message into a textbox then it will be saved in array and then it will be populated to the crollView in realtime so the user can view all the messages.
Error: No errors, it just isn't visible.
import SwiftUI
struct SingleMessageBubbleModel: Identifiable {
let id = UUID()
var text: String
var received: Bool
var timeStamp: Date
}
var messagesDBArray : [SingleMessageBubbleModel] = []
struct ContentView: View {
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
struct MessageBubble: View {
var message: SingleMessageBubbleModel
#State private var showTime = false
var body: some View {
VStack(alignment: message.received ? .leading : .trailing) {
HStack {
Text(message.text)
.padding()
.background(message.received ? Color.gray : Color.blue)
.cornerRadius(30)
}
.frame(maxWidth: 300, alignment: message.received ? .leading : .trailing)
.onTapGesture {
withAnimation {
showTime.toggle()
}
}
if showTime {
Text("\(message.timeStamp.formatted(.dateTime.hour().minute()))")
.font(.caption2)
.foregroundColor(.gray)
.padding(message.received ? .leading : .trailing, 25)
}
}
.frame(maxWidth: .infinity, alignment: message.received ? .leading : .trailing)
.padding(message.received ? .leading : .trailing)
.padding(.horizontal, 4)
}
}
Basically, when the button is pressed, your property messagesDBArray is well and truly append with the new value.
However, and it's really important to understand this point in swiftUI, nothing triggers the refresh of the view.
I suggest you two solutions:
If you don't need messagesDBArray to be outside of ContentView:
You just have to add messagesDBArray as a state in ContentView like following
struct ContentView: View {
#State var messagesDBArray : [SingleMessageBubbleModel] = []
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
If you need messagesDBArray to be outside of ContentView:
1- Create a class (ViewModel or Service or whatever you wan to call it) with messagesDBArray as a #Published property
final class ViewModel: ObservableObject {
#Published var messagesDBArray : [SingleMessageBubbleModel] = []
}
2- Observe this class in ContentView in order to append and receive the update
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(viewModel.messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: viewModel.messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
viewModel.messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
I hope that this is clear to you and that it has been useful 😉

SwiftUI: Updating a View to include a Custom Subview based on a user action is a separate view

so I am trying to have a view update to display a custom view based on a user selection from another view. This is a simple task app project I started to get a better understanding of SwiftUI and have hit my first major roadblock. The custom view is generated from a Tag object from Core Data, so it would be this information that is passed from View 2 to View 1.
I've marked where the update would take place as well as where the action is performed with TODOs. Hopefully I did a good job at explaining what I am hoping to accomplish, nothing I have tried seems to work. I am sure it's something simple but the solution is evading me.
View 1: View that needs to be updated when user returns
View 2: View where selection is made
The View that needs to be updated and its ViewModel.
struct AddTaskView: View {
//MARK: Variables
#Environment(\.managedObjectContext) var coreDataHandler
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = AddTaskViewModel()
#StateObject var taskListViewModel = TaskListViewModel()
#State private var title: String = ""
#State private var info: String = ""
#State private var dueDate = Date()
var screenWidth = UIScreen.main.bounds.size.width
var screenHeight = UIScreen.main.bounds.size.height
var body: some View {
VStack(spacing: 20) {
Text("Add a New Task")
.font(.title)
.fontWeight(.bold)
//MARK: Task.title Field
TextField("Task", text: $title)
.font(.headline)
.padding(.leading)
.frame(height: 55)
//TODO: Update to a specific color
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.cornerRadius(10)
//MARK: Task.tag Field
HStack {
Text("Tag")
Spacer()
//TODO: UPDATE TO DISPLAY TAG IF SELECTED OTHERWISE DISPLAY ADDTAGBUTTONVIEW
NavigationLink(
destination: TagListView(),
label: {
AddTagButtonView()
}
)
.accentColor(.black)
}
//MARK: Task.info Field
TextEditor(text: $info)
.frame(width: screenWidth - 40, height: screenHeight/4, alignment: .center)
.autocapitalization(.sentences)
.multilineTextAlignment(.leading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black, lineWidth: 0.5)
)
//MARK: Task.dateDue Field
DatePicker(
"Due Date",
selection: $dueDate,
in: Date()...
)
.accentColor(.black)
.font(.headline)
Spacer()
Button(action: {
viewModel.addTask(taskTitle: title, taskInfo: info, taskDueDate: dueDate)
//Dismiss View if successful
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
}
.padding()
.navigationBarTitleDisplayMode(.inline)
}
}
final class AddTaskViewModel : ObservableObject {
var coreDataHandler = CoreDataHandler.shared
#Published var tag : Tag?
func addTask(taskTitle: String, taskInfo: String, taskDueDate: Date) {
let newTask = Task(context: coreDataHandler.container.viewContext)
newTask.title = taskTitle
newTask.info = taskInfo
newTask.dateCreated = Date()
newTask.dateDue = taskDueDate
newTask.completed = false
newTask.archived = false
coreDataHandler.save()
}
}
The View where the selection is made and its ViewModel
struct TagListView: View {
#FetchRequest(entity: Tag.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Tag.title, ascending: true)]) var tagList : FetchedResults<Tag>
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = TagListViewModel()
var body: some View {
VStack {
HStack {
Text("Create a Tag")
.font(.system(size: 20))
.fontWeight(.medium)
Spacer()
NavigationLink(
destination: CreateTagView(),
label: {
Image(systemName: "plus.circle")
.font(.system(size: 25))
})
}
Divider()
.padding(.bottom, 10)
ScrollView(.vertical, showsIndicators: false, content: {
if tagList.count != 0 {
LazyVStack(spacing: 20) {
ForEach(tagList, id: \.self) { tag in
let tagColour = Color(red: tag.colourR, green: tag.colourG, blue: tag.colourB, opacity: tag.colourA)
Button {
//TODO: UPDATE ADDTASKVIEW TO DISPLAY THE SELECTED TAG
//Dismiss view
self.presentationMode.wrappedValue.dismiss()
} label: {
TagView(title: tag.title ?? "Tag", color: tagColour, darkText: false)
}
}
}
} else {
Text("Add your first tag.")
}
})
}
.padding()
}
}
final class TagListViewModel : ObservableObject {
}

SwiftUI - Passing data from arrays into structs

I'm having difficulties passing the data from the array into WeatherWidget that passes each item through WidgetView.
I believe it has something to do with how I declared #State.
// ContentView
struct WeatherWidget: View {
#State var weather = weatherData
#State var index = 0
var body: some View {
ScrollView (.vertical, showsIndicators: false){
TabView(selection: self.$index) {
ForEach(weatherData) { weather in
WidgetView(data: weather)
// Identifies current index
.tag(self.index)
}
}
.animation(.easeOut)
}
.animation(.easeOut)
}
}
// Widget
struct WidgetView: View {
#State var data: Weather
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(data.temp)
.foregroundColor(.white)
.font(.system(size: 24))
.fontWeight(.semibold)
.padding(.bottom, 16)
Text(data.city)
.foregroundColor(.white)
.padding(.bottom, 4)
Text(data.range)
.foregroundColor(.gray)
}
Image(systemName: data.icon)
.font(.system(size: 32, weight: .medium))
.foregroundColor(.yellow)
}
.padding()
.frame(width: 185, height: 169)
.background(RoundedRectangle(cornerRadius: 24, style: .continuous).fill(Color.black.opacity(0.8)))
}
}
// Weather Struct
struct Weather : Hashable, Identifiable {
var id = UUID()
var temp : String
var city : String
var range : String
var icon : String
}
// Data Array
var weatherData = [
Weather(temp: "26°C", city: "Toronto, ON", range: "17°C / 28°C", icon: "sun.max"),
Weather(temp: "17°C", city: "Waterloo, ON", range: "14°C / 24°C", icon: "cloud.rain"),
Weather(temp: "31°C", city: "Whitby, ON", range: "24°C / 32°C", icon: "cloud.bolt.rain")
]