I've got myself in a bit of a pickle. I'm learning swift via a variety of online tutorials and at the same time coding a side project as further self learning. The side project will be a VERY basic football manager style app which I figure will give me a bit of UI, data handling, and algorithm refactoring practice. It is this side project where I have hit a blocker. I understand the principle of state variables to maintain a permanent link between an app state and the UI and I also understand the general principle of using observable objects / environment and and #Published to allow those variables to be available in multiple "views" however the implementation / syntax is eluding me.
The (lengthy) code below basically sets up a home screen on the UI where two teams abilities are compared. This is done via ability bars and a radar chart. Both the ability bar and the radar chart work correctly but I can't seem to link them to the same variable. The ability bar uses a series of #State variables called in the main content.view and then passed via $bindings from a child view to its parent but the radar plot is populated by local variables within a data array. I can't work out how to put the state variables into the array, either as is, or by using observable or environmental variables. Basically I want to declare the 6 variables in one location and then use them inside the array and also inside the various views.
I'd be really appreciative if someone could offer some suggestions or indeed to point me in a different direction if I am completely on a tangent with this.
By way of a quick summary...
First I have an ability bar view which creates something similar to a progress bar...
//Ability Bar View
struct abilityBar: View {
#Binding var value: Double
var color1: Color
var color2: Color
var title: String
var barHeight: Float
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: -3) {
HStack {
Text("\(title)")
.modifier(lightFont())
Spacer()
Text("\(self.value, specifier: "%.1f")")
.modifier(heavyFont())
}
Spacer()
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 4)
.frame(width: geometry.size.width, height: CGFloat(barHeight))
.foregroundColor(.white)
.opacity(0.075)
RoundedRectangle(cornerRadius: 4)
.fill(LinearGradient(
gradient: Gradient(colors: [color1, color2]),
startPoint: .leading,
endPoint: .trailing))
.frame(width: ((CGFloat(self.value)/10) * geometry.size.width), height: CGFloat(barHeight))
}
}
}
}
}
Second I have a team widget view that nests these ability bars and is passed variables by way of $Bindings
//Team Widget View
struct teamWidget: View {
#Binding var defence: Double
#Binding var midfield: Double
#Binding var attack: Double
var color1: Color
var color2: Color
var location: String
var teamName: String
var position: String
var body: some View {
ZStack {
Rectangle()
.frame(height: 185.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: .black, radius: 4, x: 4, y: 4)
HStack {
Rectangle()
.fill(LinearGradient(
gradient: Gradient(colors: [color1, color2]),
startPoint: .top,
endPoint: .bottom))
.frame(height: 185.0)
.frame(width: 8)
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0.0) {
Text("\(location)")
.modifier(lightFont())
Text("\(teamName)")
.modifier(heavyFont())
Text("League Position")
.modifier(lightFont())
Text("\(position)")
.modifier(heavyFont())
}
VStack(spacing: 4.0) {
abilityBar(value: $defence, color1: color1, color2: color2, title: "Defence", barHeight: 9.0)
.frame(height: 28)
abilityBar(value: $midfield, color1: color1, color2: color2, title: "Midfield", barHeight: 9.0)
.frame(height: 28)
abilityBar(value: $attack, color1: color1, color2: color2, title: "Attack", barHeight: 9.0)
.frame(height: 28)
}
}
Spacer()
}
}
}
}
These are populated in the main content view via #State variables. Also in the main content view is a data array that is used to draw a radar chart (main code in a separate .swift file. What I cant work out how to do is call the same #State variables in this array. Or whether I need to define the state variables differently (observable / environment etc). For reference in this large body of code I ahve commented 3 lines (1) - where the state variables are declared (2) where the data array for the radar chart is declared and where I currently cant get state variables or equivalent to work and (3) where the ability bars are declared (work with state variables)
Apologies for the long main / lots of code. I couldn't think of a way to cut this down further.
enum RayCase:String, CaseIterable {
case defence = "Def"
case midfield = "Mid"
case attack = "Att"
}
struct DataPoint:Identifiable {
var id = UUID()
var entrys:[RayEntry]
var color:Color
init (defence:Double, midfield:Double, attack:Double, color:Color) {
self.entrys = [
RayEntry(rayCase: .defence, value: defence),
RayEntry(rayCase: .attack, value: attack),
RayEntry(rayCase: .midfield, value: midfield),
]
self.color = color
}
}
struct ContentView: View {
// 1 THIS IS WHERE STATE VARIABLES ARE DECLARED
#State var team1Defence: Double = Double.random(in: 1.0...9.9)
#State var team1Midfield: Double = Double.random(in: 1.0...9.9)
#State var team1Attack: Double = Double.random(in: 1.0...9.9)
#State var team2Defence: Double = Double.random(in: 1.0...9.9)
#State var team2Midfield: Double = Double.random(in: 1.0...9.9)
#State var team2Attack: Double = Double.random(in: 1.0...9.9)
#State var playMatchIsVisible: Bool = false
let mainRed = Color(red: 245/255, green: 39/255, blue: 85/255)
let highlightRed = Color(red: 245/255, green: 85/255, blue: 141/255)
let mainBlue = Color(red: 62/255, green: 219/255, blue: 208/255)
let highlightBlue = Color(red: 119/255, green: 237/255, blue: 194/255)
let dimensions = [
Ray(maxVal: 10, rayCase: .attack),
Ray(maxVal: 10, rayCase: .midfield),
Ray(maxVal: 10, rayCase: .defence),
]
// 2 THIS IS THE DATA ARRAY WHERE I CANT GET STATE VARIABLES TO WORK, THE SAME 6 DECLARED VARIABLES NEED TO POPULATE THIS ARRAY
let data = [
DataPoint(defence: 5, midfield: 8, attack: 7, color: .red),
DataPoint(defence: 5, midfield: 2, attack: 9, color: .blue),
]
var body: some View {
//Background Z-Stack Container
ZStack {
LinearGradient(gradient: Gradient(colors: [(Color(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.32941176470588235)), (Color(red: 0.058823529411764705, green: 0.06666666666666667, blue: 0.12941176470588237))]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
//Main VStack
VStack {
//Row 1 Main HStack
HStack {
//Date / Fixture ZStack
ZStack {
Rectangle()
.frame(height: 70.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: /*#START_MENU_TOKEN#*/.black/*#END_MENU_TOKEN#*/, radius: 4, x: 4, y: 4)
//Date Fixture HStack to ensure left alignment
HStack {
// Date / Fixture VStack to put text on top of each other (6)
VStack(alignment: .leading, spacing: 4.0) {
Text("Date")
.modifier(heavyFont())
Text("Saturday 23rd October")
.modifier(lightFont())
Text("League 2 Fixture")
.modifier(heavyFont())
}
Spacer() //Forces text to LHS of widget
}
.padding(.leading, 7)
}
.padding(.leading, 15)
.padding(.trailing, 6)
// Play Match Stack (7)
ZStack {
Rectangle()
.frame(height: 70.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: /*#START_MENU_TOKEN#*/.black/*#END_MENU_TOKEN#*/, radius: 4, x: 4, y: 4)
// Play Match HStack to ensure correct left alignment (8)
HStack {
Button(action: {
self.playMatchIsVisible = true
}) {
Image("playFull")
Text("Play Match")
.modifier(heavyFont())
}
.alert(isPresented: $playMatchIsVisible, content: {
return Alert(title: Text("Are you sure you want to kick off?"), primaryButton: .default(Text("Yes, Let's play!")), secondaryButton: .default(Text("No, not yet.")))
})
}
}
.padding(.leading, 6)
.padding(.trailing, 15)
}
.padding(.top, 15)
//Row 2 HStack
HStack {
// 3 THIS IS WHERE STATE VARIABLES ARE CALLED TO POPULATE THE ABILITY BARS AND WORK IN THIS CONTEXT AND WHEEN PASSED AS BINDINGS INTERNALLY
teamWidget(defence: $team1Defence, midfield: $team1Midfield, attack: $team1Attack, color1: highlightRed, color2: mainRed, location: "Home", teamName: "Oxford United", position: "14")
.padding(.leading, 15)
.padding(.trailing, 6)
teamWidget(defence: $team2Defence, midfield: $team2Midfield, attack: $team2Attack, color1: highlightBlue, color2: mainBlue, location: "Away", teamName: "Luton Town", position: "3")
.padding(.leading, 6)
.padding(.trailing, 15)
}
.padding(.top, 15)
VStack {
RadarChartView(width: 370, MainColor: Color.init(white: 0.8), SubtleColor: Color.init(white: 0.6), quantity_incrementalDividers: 4, dimensions: dimensions, data: data)
}
.frame(height: 275)
.padding(.top, 15)
Spacer() //Forces all to top in main stack
HStack {
Button(action: {}) {
Image("home")
}
Spacer()
Button(action: {}) {
Image("barcode")
}
Spacer()
Button(action: {}) {
Image("squad")
}
Spacer()
Button(action: {}) {
Image("fixtures")
}
Spacer()
Button(action: {}) {
Image("settings")
}
}
.padding(.leading, 20)
.padding(.trailing, 20)
.padding(.bottom, 15)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The net result of this looks something like this... but currently the ability bars and radar charts dont have the same single truth data set.
content view screen shot
Related
I'm trying to re-crate a simple app from code.org, and while trying, I came across the issue that I can't change the screen how I imagined.
I wanted it so that theres 2 ContentViews. One is for the home page, and the second is for the first question (I will add more in the future).
In the Home view, there is a rectangle with some text inside. There is a .onTapGesture argument under the Rectangle(), in which it should change the #State variable to Question1() from Home().
However, it says that it can't find the State Variable in the scope. I also tried EnvironmentalObject, but I don't know how to format it, and to be honest, I think it's because I'm not searching for the right thing.
Here's the entire project, in which should be run on an iPhone 12 Pro simulator:
struct ContentView: View {
#EnvironmentObject private var showingScreen = Home()
var body: some View {
showingScreen
}
}
struct Home: View {
var body: some View {
ZStack {
Color(red: 115 / 255, green: 235 / 255, blue: 156 / 255)
.edgesIgnoringSafeArea(.all)
VStack {
Text("The Ultimate Pet Chooser!")
.bold()
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(Color(red: 77 / 255, green: 87 / 255, blue: 95 / 255))
.position(x: 200, y: 80)
Rectangle()
.fill(Color(red: 130 / 255, green: 0 / 255, blue: 255 / 255))
.frame(width: 300, height: 250)
.position(x: 200, y: 150)
.onTapGesture {
showingScreen = Question1()
}
Text("Click here to start!")
.fontWeight(.heavy)
.font(.system(size: 50))
.bold()
.multilineTextAlignment(.center)
.foregroundColor(.white)
.position(x: 200, y: -105)
}
}
}
}
struct Question1: View {
var body: some View {
Text("Hello")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
From what I understand what you try to achieve, you want to have a Home view and it shall provide a button (or so) to switch to the Question View?
Is that understanding is correct?
If so, here are 2 approaches you could follow:
Navigation View with Navigation Link
You have 2 views HomeView() and QuestionView(). The HomeView() contains a NavigationView and within that NavigationView you have a NavigationLink to open the QuestionView(). If you press the that link the QuestionView() slides in from the right side. In a NavigationView you can set up some options, for example a title, the appearance of the title, a back button (is automatically there, etc...)
This approach would look like:
//
// ContentView.swift
// PetChooser
//
// Created by Sebastian Fox on 30.08.22.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
HomeView()
}
}
}
struct HomeView: View {
var body: some View {
NavigationView() {
VStack(){
Text("The Ultimate Pet Chooser!")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(Color(red: 77/255, green: 87/255, blue: 95/255))
NavigationLink(destination: QuestionView()) {
Text("Click here to start!")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(.white)
.clipShape(Rectangle()) // actually, that is not even necessary, the frame makes it a rectangle anyway.
.frame(width: 300, height: 250)
.background(Color(red: 130/255, green: 0/255, blue: 255/255))
}
}
}
}
}
struct QuestionView: View {
var body: some View {
VStack {
Text("Here the first Question")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(Color(red: 77/255, green: 87/255, blue: 95/255))
}
}
}
Here a gif of how it looks like
#State var to handle displayed view controlled by buttons#
In this approach you also have the two views HomeView() and QuestionView()additional to that you have one var in your ContentView() that handles which view will be shown. In this approach the variable that handles the selected view will be initialized to show the HomeView() (I created an Enum with the available views, but you can use Int e.g.: HomeView() = 0 and QuestionView() = 1, or even String values as well). The HomeView() now has a got a button that changes the value of variable that handles the shown view to the value that represents the QuestionView() (on the QuestionView() there is a button to change the value of the variable to show the HomeView(). To make the variable from ContentView() avaibalew in the (Sub)Views, there is a binding, so you can access the variable from all the views:
//
// ContentView.swift
// PetChooser
//
// Created by Sebastian Fox on 30.08.22.
//
import SwiftUI
struct ContentView: View {
#State var selectedPage: SelectedPage = .home
var body: some View {
// YOU CAN DO A SWITCH CASE AS WELL
if selectedPage == .home {
HomeView(selectedPage: $selectedPage)
} else if selectedPage == .question {
QuestionView(selectedPage: $selectedPage)
} else {
VStack() {
Text("ERROR, PAGE DOES NOT EXIST")
}
}
}
}
struct HomeView: View {
#Binding var selectedPage: SelectedPage
var body: some View {
NavigationView() {
VStack(){
Text("The Ultimate Pet Chooser!")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(Color(red: 77/255, green: 87/255, blue: 95/255))
Button(action: {
selectedPage = .question
}) {
HStack() {
Text("Click here to start!")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(.white)
}
.clipShape(Rectangle()) // actually, that is not even necessary, the frame makes it a rectangle anyway
.frame(width: 300, height: 250)
.background(Color(red: 130/255, green: 0/255, blue: 255/255))
}
}
}
}
}
struct QuestionView: View {
#Binding var selectedPage: SelectedPage
var body: some View {
VStack {
Text("Here the first Question")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(Color(red: 77/255, green: 87/255, blue: 95/255))
Button(action: {
selectedPage = .home
}) {
HStack() {
Text("back")
.font(.system(size: 50))
.fontWeight(.heavy)
.foregroundColor(.white)
}
.clipShape(Rectangle())
.frame(width: 300, height: 250)
.background(Color(red: 130/255, green: 0/255, blue: 255/255))
}
}
}
}
enum SelectedPage {
case home
case question
}
And the gif
When adding a shadow to my view in a Grid, the scrolling experience is bad. It feels like the Frame Rate is dropping. I came across this post, option 1 made my whole background for my view the same color as my shadow. I Don't really know how to implement UIViewRepresentable in option 2.
So how would I be able to use UIViewRepresentable, or is there a better way to do this.
MRE CODE
struct ContentView: View {
#State var gridSpacing: CGFloat = 8
let columns: [GridItem] = [GridItem(.flexible(),spacing: 8), GridItem(.flexible(),spacing: 8),GridItem(.flexible(),spacing: 8)]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: gridSpacing) {
ForEach(0..<200) { x in
VStack(spacing: 8) {
Image("sumo-deadlift") //<----- Replace Image
.resizable()
.scaledToFit()
.background(.yellow)
.clipShape(Circle())
.padding(.horizontal)
.shadow(color: .blue, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
Text("Sumo Deadlift")
.font(.footnote.weight(.semibold))
.multilineTextAlignment(.center)
.lineLimit(2)
.frame(maxWidth: .infinity)
Text("Legs")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.all)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(color: .red, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
}
}
.padding(.all, 8)
}
.background(.ultraThinMaterial)
}
}
I just upgraded my phone from 15.x (I think 15.2) to 15.3, and my SwiftUI layout is broken, seems to be a List border issue.
Here is the code, followed by a screen shot of normal behavior (13.x to 15.x) and then what I see in 15.3
struct SessionView: View {
var title: String
var panel: Int
var index: Int
var range: [Int] = [0,2,3,4]
#EnvironmentObject var state: MainViewModel
#EnvironmentObject var content: ContentViewModel
#ViewBuilder
var body: some View {
VStack() {
// -- Header
HStack() {
Text(" ")
Image(self.state.panelIcon(panel: panel)).resizable().frame(width: 12.0, height: 12.0)
Text(title)
Spacer()
}.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.onTapGesture {
showDetailDialog()
}
// -- Rows
List {
ForEach(0..<5) { i in
if i != 1 { // Skip IP address
HStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: -4, content: {
Text("\(state.keyValues[i+index]!.key)").frame(minWidth: 120, maxHeight: 20, alignment: .leading)
.font(Font.system(size: 15, design: .default)).padding(0)
Text("\(state.keyValues[i+index]!.value)").frame(maxHeight: 20, alignment: .leading)
.font(Font.system(size: 15, design: .default)).padding(0)
}).frame(height: 10)
}
}
}.environment(\.defaultMinListRowHeight, 10)
.frame(height: 4*20+20)
.listStyle(DefaultListStyle()).environment(\.defaultMinListRowHeight, 8).onAppear {
UITableView.appearance().isScrollEnabled = false
}.layoutPriority(1)
}.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(red: 0.8, green: 0.8, blue: 0.8), lineWidth: 1.25)
).background(Color.white)
}
.
.
.
}
I had this problem too. However, it is not a border issue. When you updated your iPhone, the default list style for SwiftUI changed. The old default style is now called PlainListStyle(). Use that instead to get the old look back.
List {
}.listStyle(PlainListStyle())
Problem:I have a View that I needed to place multiple (2) views that contained: 1 Image + 1 Text. I decided to break that up into a ClickableImageAndText structure that I called on twice. This works perfectly if the image is a set size (64x64) but I would like this to work on all size classes. Now, I know that I can do the following:
if horizontalSizeClass == .compact {
Text("Compact")
} else {
Text("Regular")
}
but I am asking for both Different Size Classes and Same Size Classes such as the iPhone X and iPhone 13 which are the same.
Question:How do I alter the image for dynamic phone sizes (iPhone X, 13, 13 pro, etc) so it looks appropriate for all measurements?
Code:
import SwiftUI
struct ClickableImageAndText: View {
let image: String
let text: String
let tapAction: (() -> Void)
var body: some View {
VStack {
Image(image)
.resizable()
.scaledToFit()
.frame(width: 64, height: 64)
Text(text)
.foregroundColor(.white)
}
.contentShape(Rectangle())
.onTapGesture {
tapAction()
}
}
}
struct InitialView: View {
var topView: some View {
Image("Empty_App_Icon")
.resizable()
.scaledToFit()
}
var bottomView: some View {
VStack {
ClickableImageAndText(
image: "Card_Icon",
text: "View Your Memories") {
print("Tapped on View Memories")
}
.padding(.bottom)
ClickableImageAndText(
image: "Camera",
text: "Add Memories") {
print("Tapped on Add Memories")
}
.padding(.top)
}
}
var body: some View {
ZStack {
GradientView()
VStack {
Spacer()
topView
Spacer()
bottomView
Spacer()
}
}
}
}
struct InitialView_Previews: PreviewProvider {
static var previews: some View {
InitialView()
}
}
Image Note:My background includes a GradientView that I have since removed (thanks #lorem ipsum). If you so desire, here is the GradientView code but it is unnecessary for the problem above.
GradientView.swift
import SwiftUI
struct GradientView: View {
let firstColor = Color(uiColor: UIColor(red: 127/255, green: 71/255, blue: 221/255, alpha: 1))
let secondColor = Color(uiColor: UIColor(red: 251/255, green: 174/255, blue: 23/255, alpha: 1))
let startPoint = UnitPoint(x: 0, y: 0)
let endPoint = UnitPoint(x: 0.5, y: 1)
var body: some View {
LinearGradient(gradient:
Gradient(
colors: [firstColor, secondColor]),
startPoint: startPoint,
endPoint: endPoint)
.ignoresSafeArea()
}
}
struct GradientView_Previews: PreviewProvider {
static var previews: some View {
GradientView()
}
}
Effort 1:Added a GeometryReader to my ClickableImageAndText structure and the view is automatically changed incorrectly.
struct ClickableImageAndText: View {
let image: String
let text: String
let tapAction: (() -> Void)
var body: some View {
GeometryReader { reader in
VStack {
Image(image)
.resizable()
.scaledToFit()
.frame(width: 64, height: 64)
Text(text)
.foregroundColor(.white)
}
.contentShape(Rectangle())
.onTapGesture {
tapAction()
}
}
}
}
Effort 2:Added a GeometryReader as directed by #loremipsum's [deleted] answer and the content is still being pushed; specifically, the topView is being push to the top and the bottomView is taking the entire space with the addition of the GeometryReader.
struct ClickableImageAndText: View {
let image: String
let text: String
let tapAction: (() -> Void)
var body: some View {
GeometryReader{ geo in
VStack {
Image(image)
.resizable()
.scaledToFit()
//You can do this and set strict size constraints
//.frame(minWidth: 64, maxWidth: 128, minHeight: 64, maxHeight: 128, alignment: .center)
//Or this to set it to be percentage of the size of the screen
.frame(width: geo.size.width * 0.2, alignment: .center)
Text(text)
}.foregroundColor(.white)
//Everything moves to the left because the `View` expecting a size vs stretching.
//If yo want the entire width just set the View with on the outer most View
.frame(width: geo.size.width, alignment: .center)
}
.contentShape(Rectangle())
.onTapGesture {
tapAction()
}
}
}
The possible solution is to use screen bounds (which will be different for different phones) as reference value to calculate per-cent-based dynamic size for image. And to track device orientation changes we wrap our calculations into GeometryReader.
Note: I don't have your images, so added white borders for demo purpose
struct ClickableImageAndText: View {
let image: String
let text: String
let tapAction: (() -> Void)
#State private var size = CGFloat(32) // some minimal initial value (not 0)
var body: some View {
VStack {
Image(image)
.resizable()
.scaledToFit()
// .border(Color.white) // << for demo !!
.background(GeometryReader { _ in
// GeometryReader is needed to track orientation changes
let sizeX = UIScreen.main.bounds.width
let sizeY = UIScreen.main.bounds.height
// Screen bounds is needed for reference dimentions, and use
// it to calculate needed size as per-cent to be dynamic
let width = min(sizeX, sizeY)
Color.clear // % (whichever you want)
.preference(key: ViewWidthKey.self, value: width * 0.2)
})
.onPreferenceChange(ViewWidthKey.self) {
self.size = max($0, size)
}
.frame(width: size, height: size)
Text(text)
.foregroundColor(.white)
}
.contentShape(Rectangle())
.onTapGesture {
tapAction()
}
}
}
I want to hide some text behind the NavigationBarTitle and show it when user press a button, it's working fine. The only problem is with the animation. At loading we see the text moving behind the NavigationBarTitle and that's not what i want.
How can i fix this?
I tried with offset and position but it's not working...
Code :
import SwiftUI
struct TestZStackNavigationView: View {
#State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
// .offset(y: self.isShowed ? -315 : -355)
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
.animation(.easeIn(duration: 0.5))
Button(action: {
self.isShowed.toggle()
}) {
Text("click me")
}
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}
struct TestZStackNavigationView_Previews: PreviewProvider {
static var previews: some View {
TestZStackNavigationView()
}
}
There are two possibilities
make animation per-state (and no other changes)
.animation(.easeIn(duration: 0.5), value: isShowed)
replace implicit animation as modifier and add explicit animation in action
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
// .animation(.easeIn(duration: 0.5)) // remove from here
Button(action: {
withAnimation(Animation.easeIn(duration: 0.5)) { // << add this
self.isShowed.toggle()
}
}) {
Text("click me")
}
I want to use it now with an ObservedObject but i got that same behavior as first code. Why?
struct TestZStackNavigationView: View {
// #State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
#ObservedObject var online = NetStatus()
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
.position(x: screenSize.width / 2, y: online.connected ? -40 : 20)
.animation(.easeIn(duration: 0.5), value: online.connected)
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}