SwiftUI .overlay() causing data to be displayed incorrectly - swift

I'm working on an app that calls an api and displays the data on 6 different cards. In the card view the data displays correctly. Once I place that view into another using .overlay() the UI is perfect but the data on each card is the same. Without .overlay() the data is correct but the UI is wrong. I've tried changing my ViewModel variable in the views to #EnvironmentObject, #StateObject, & #ObservedObject with no luck. I can't figure out if this is a bug or I am missing something. Any help is greatly appreciated.
This view displays data correctly:
import SwiftUI
struct TopActivityCardView: View {
// #EnvironmentObject var vm: ViewModel
#StateObject var vm = ViewModel()
var timePeriod = ""
var body: some View {
ZStack {
ScrollView{
ForEach(vm.fetch(), id: \.title) { item in
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.darkBlue)
.frame(height: 120, alignment: .center)
.padding(.top, 80)
.overlay(
ActivityTitleView(activityTitle: item.title)
).overlay(
ActivityHoursView(workHours: item.timeframes.weekly.current)
).overlay(
ActivityEllipisView()
).overlay(
TimePeriodView(timePeriod: "Last Week", weeklyHrs:
item.timeframes.weekly.previous)
)
}
}
}
}
}
struct TopActivityCardView_Previews: PreviewProvider {
static var previews: some View {
TopActivityCardView()
.environmentObject(ViewModel())
}
}
This view is where the problem occurs:
import SwiftUI
struct RectCardView: View {
var colorArray: [(colorName: Color, imageName: String)] = [(Color.work, "icon-work"),
(Color.play, "icon-play"), (Color.study, "icon-study"), (Color.exercise, "icon-
exercise"), (Color.social, "icon-social"), (Color.selfCare, "icon-self-care")]
// #StateObject var vm = ViewModel()
#ObservedObject var vm = ViewModel()
var body: some View {
ZStack{
ScrollView{
ForEach(colorArray, id: \.imageName) { colorName, imageName in
RoundedRectangle(cornerRadius: 20)
.foregroundColor(colorName)
.frame(height: 110, alignment: .center)
.overlay(
Image(imageName)
.brightness(-0.2)
.frame(width: 60, height: 20, alignment: .topLeading)
.offset(x: 103, y: -60)
).clipped()
/// If I take .overlay() off the cards display correct data but UI is
not correct.
/// With overlay UI is correct but data on cards is all the same.
.overlay{
TopActivityCardView()
.padding(.top, -40)
}
.padding(.top, 20)
}
}
}
}
}
struct RectCardView_Previews: PreviewProvider {
static var previews: some View {`enter code here`
RectCardView()
.environmentObject(ViewModel())
}
}

In TopActivityCardView, you are making a new view model (#StateObject var vm = ViewModel()).
It isn't using the same view model as RectCardView, so the data is different.
You could pass the view into TopActivityCardView by using an environment object:
// code…
struct TopActivityCardView: View {
#EnvironmentObject var vm: ViewModel
// more code…
}
.overlay{
TopActivityCardView()
.environmentObject(vm)
.padding(.top, -40)
}

Related

SwiftUI Tutorial: Scrumdigger Spacer() issue

I have been working through the SwiftUI Scrumdigger tutorial (https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger) and have reached Step 7 of "Creating a Card View". Here is where my experience deviates from what is presented by the tutorial.
Here is the code both I and the tutorial are using:
import SwiftUI
struct CardView: View {
let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text(scrum.title)
.font(.headline)
Spacer()
HStack {
Label("\(scrum.attendees.count)", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView(scrum: scrum)
.background(scrum.theme.mainColor)
.previewLayout(.fixed(width: 400, height: 60))
}
}
Here is what I see:
And here is what the tutorial displays:
I have tried moving the Spacer() from line 9 to line 13 but the issue is the same.
Can reproduce without the reliance on the external Scrum data using this:
import SwiftUI
struct CardView: View {
//let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
Spacer()
HStack {
Label("3", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
//static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView()
.previewLayout(.fixed(width: 400, height: 60))
}
}
Can anyone advise as to what might be going wrong here?

Having trouble with showing another view

I'm currently trying to input another listview in my contentView file to test if it'll show, but for some reason it isn't showing the list. I'm having a bit of trouble understanding why this is happening as I am not receiving any error message.
This is the code for the list file
import SwiftUI
extension Image{
func anotherImgModifier() -> some View{
self
.resizable()
.scaledToFill()
.frame( width: 75, height: 75)
.cornerRadius(9)
}
}
struct PokeListView: View {
#State var imgURL: String = ""
#EnvironmentObject var pokeWebService: PokeWebService
//functions
// func loadImage() async -> [Image]{
// for
// }
var body: some View {
NavigationView {
List( pokeWebService.pokeList?.results ?? [], id: \.id){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url, counter: 4, name: pokemon.name)) {
AsyncImage(url:URL(string: "https://play.pokemonshowdown.com/sprites/bw/\(pokemon.name).png")){ image in
image.anotherImgModifier()
}
placeholder: {
Image(systemName: "photo.circle.fill").iconModifer()
}.padding(40)
Text(pokemon.name.uppercased()).font(.system(size: 15, weight: .heavy, design: .rounded))
.foregroundColor(.gray)
.task{
do{
try await pokeWebService.getPokemonFromPokemonList(from: pokemon.url)
} catch{
print("---> task error: \(error)")
}
}
}
}
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(PokeWebService())
}
}
This is the code for the ContentView where I was trying to input the list file.
import SwiftUI
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
#StateObject var pokeWebService = PokeWebService()
let gbImg = Image("pokeball").resizable()
#State private var gridLayout: [GridItem] = [ GridItem(.flexible()), GridItem(.flexible())]
#State private var gridColumn: Int = 2
#State var selection: Int? = nil
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false, content: {
VStack(alignment: .center, spacing: 15, content: {
Spacer()
NewsCapsule()
//GRID
//BERRIES, POKEMON, GAMES
GroupBox(label: Label{
Text("PokéStuff")
} icon: {
Image("pokeball").resizable().scaledToFit().frame(width: 30, height: 30, alignment: .leading)
}
, content: {
PokeListView()
}).padding(.horizontal, 20).foregroundColor(.red)
})//:VSTACK
})//:SCROLLVIEW
.navigationBarTitle("Pokemon",displayMode: .large)
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Image(systemName: "moon.circle")
.resizable()
.scaledToFit()
.font(.title2)
.foregroundColor(.red)
})
})
}//:NAVIGATIONBAR
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NewsWebService())
.environmentObject(PokeWebService())
}
}
How would I get to fix this?
EDIT-1:
with further tests, this is what worked for me:
in ContentView, add .frame(height: 666) to the VStack {...}.
This is the reason why you do not see anything. You need a frame height.
Also in ContentView, add .environmentObject(pokeWebService) to the NavigationView,
and just use PokeListView(). This is to pass the pokeWebService
to that view. After that, all works for me. You may want to experiment
with different frame sizes and such likes. You should also remove the NavigationView from your PokeListView, there is no need for it.

How to resize Text View in Swift

I'm doing some work for the project(Newbie programmer) in SwiftUI.
I try to resize the textbox yet IDK how. Could anyone help me about this?
import SwiftUI
struct ContentView: View {
#Binding var document: NavKov20193123Document
var body: some View {
TextEditor(text: $document.text)
.foregroundColor(.gray)
.lineSpacing(3)
.allowsTightening(false)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(document: .constant(NavKov20193123Document()))
}
}
you can use .frame(width: , height: )
struct ContentView: View {
var body: some View {
return Text("Hello world")
.font(.system(size: 50, weight: .bold))
.minimumScaleFactor(0.5)
.frame(width: 100, height: 100)
.background(Color.yellow)
}
}

SwiftUI: changing view using "if else"

I’m trying to change a view based on a selection made with a button. I’m using Combine and SwiftUI.
I create a view router class with a #Published var:
import Foundation
import SwiftUI
import Combine
class ViewRouter: ObservableObject {
#Published var currentView = "folder"
}
I have my button struct:
import SwiftUI
import Combine
struct MultiButton: View {
#ObservedObject var viewRouter = ViewRouter()
#State var showButton = false
var body: some View {
GeometryReader { geometry in
ZStack{
// open multi button
Button(action: {
withAnimation {
self.showButton.toggle()
}
}) {
Image(systemName: "gear")
.resizable()
.frame(width: 40, height: 40, alignment: .center)
}
if self.showButton {
Multi()
.offset(CGSize(width: -30, height: -100))
}
}
}
}
}
struct MultiButton_Previews: PreviewProvider {
static var previews: some View {
MultiButton()
}
}
struct Multi: View {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
HStack(spacing: 10) {
ZStack {
Button(action: {
self.viewRouter.currentView = "folder"
debugPrint("\(self.viewRouter.currentView)")
}) {
ZStack{
Circle()
.foregroundColor(Color.blue)
.frame(width: 70, height: 70)
Image(systemName: "folder")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.frame(width: 70, height: 70)
.foregroundColor(.white)
}.shadow(radius: 10)
}
}
Button(action: {
self.viewRouter.currentView = "setting"
debugPrint("\(self.viewRouter.currentView)")
}, label: {
ZStack {
Circle()
.foregroundColor(Color.blue)
.frame(width: 70, height: 70)
Image(systemName: "gear")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.frame(width: 70, height: 70)
.foregroundColor(.white)
}.shadow(radius: 10)
})
}
.transition(.scale)
}
}
And the contentView:
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
VStack{
MultiButton()
if viewRouter.currentView == "folder" {
Text("folder")
} else if viewRouter.currentView == "setting"{
Text("setting")
}
}
}
}
Why is my text is not changing from the folder to setting the base when the button is pressEd?
The var is #Published; it should publish the change and update the main view.
Did I miss something?
You have multiple instances of ViewRouter with each view having its own instance. If you change that instance, the other instances are not going to change.
To solve this, you either have to pass one instance around manually:
struct MultiButton {
#ObservedObject var viewRouter: ViewRouter
...
}
struct ContentView {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
VStack {
MultiButton(viewRouter: self.viewRouter)
...
}
}
}
Or you can declare it as an environment object:
struct MultiButton {
#EnvironmentObject var viewRouter: ViewRouter
...
}
And then inject it for example when you create your view hierarchy in your scene delegate:
let contentView = ContentView()
.environmentObject(ViewRouter())
Note that when injecting the environment object in the scene delegate, it will not be available in every preview of a view that uses the ViewRouter, unless you manually inject it there as well:
static var previews: some View {
ContentView()
.environmentObject(ViewRouter())
}

SwiftUI - Image offset preview issue

I have a navigation view which is a root view. After user login I would like to show Welcome View where I want to show user's image and user name.
Currently preview is a bit different to my device screen:
You may noticed that there is a Login back button on the simulator screen. Probably that's why I have the differences here.
Here is my code:
struct WelcomeView: View {
#EnvironmentObject var userService: UserService
var body: some View {
VStack {
Image("MountainWelcomBackground").resizable().frame(height: 300).edgesIgnoringSafeArea(.top)
CircleImage(image: userService.user.image!)
.offset(y: -220)
.frame(height: 140)
.frame(width: 140)
VStack(alignment: .leading) {
HStack(alignment: .top) {
Spacer()
Text(userService.user.username)
.font(.headline)
Spacer()
}
}
.offset(y: -200)
Spacer()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
let userService = UserService()
userService.user = User(username: "Alex Matrosov", email: "test.developer#gmail.com", avatar: nil, image: Image("ManPlaceholderAvatar"))
return WelcomeView().environmentObject(userService)
}
}
struct CircleImage: View {
var image: Image
var body: some View {
image
.resizable()
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
.aspectRatio(contentMode: .fit)
}
}
How to make preview the same as on simulator? Probably I need to add Navigation view to preview somehow or maybe I set offset in a wrong way. Actually I just followed apple swiftui tutorial and they do this ui like me.
Offset depends on parent container, which differs in your case for real app running in Simulator and Preview showing only Welcome view.
Here is PreviewProvided to have same
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
let userService = UserService()
userService.user = User(username: "Alex Matrosov", email: "test.developer#gmail.com", avatar: nil, image: Image("icon"))
return NavigationView {
WelcomeView().environmentObject(userService)
}
}
}