Fade-in/out animation with a boolean flag - swiftui

I am trying to implement a simple "tap to toggle the visibility of the UI" in SwiftUI with fade in/out animation. The following code animates the fade-in effect of the Text element as I expected, but it immediately hides the Text element when isVisible become false.
I'd like to understand why this code does not work, and how to fix it in the most natural way.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm using Xcode 12.5 on Big Sur, and my iPhone is running iOS 14.5.1.

Thanks to Erik Philips, here is the answer.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.zIndex(1)
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
.zIndex(2)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

SwiftUI View doesn't change when using button and #State / #Binding

Long story short this is an onboarding view after a user goes through auth. My main navigation of the app uses navigationview but I can't use that for onboarding. I've put a fullscreencover over the main screen for this onboarding stuff.
However, when trying to simply navigate between the views from different files between the onboarding screens, the button doesn't show the other view. I've tried everything under the son from #Appstorage and #Environment stuff but can't get it to work.
Also please note I cut off the bottom of the rest of the file as that had nothing to do with this logic.
import SwiftUI
struct OnboardingTestView: View {
#State var shouldShowOnboarding = true
var body: some View {
if shouldShowOnboarding {
OffsetChoicesView(shouldShowOnboarding: $shouldShowOnboarding)
}
if shouldShowOnboarding == false {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
#Binding var shouldShowOnboarding: Bool
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
shouldShowOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}
You can try something like this - utilizes #AppStorage
I was not quite sure what the OffsetChoiceView() parameters are supposed to be.. so I just removed them in the example. This should be whatever your onboarding view is called.
import SwiftUI
struct OnboardingTestView: View {
//#State var shouldShowOnboarding = true
#AppStorage("showOnboarding") var showOnboarding: Bool = true
var body: some View {
if (showOnboarding == true) {
OffsetChoicesView()
} else if (showOnboarding == false) {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
//#Binding var shouldShowOnboarding: Bool
#AppStorage("showOnboarding") var showOnboarding: Bool = false
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
showOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}

SwiftUI image in modal sheets for Apple Watch

I would like to create a modal sheets with an image in the body, like in Human Interface Guidelines. Could you please give me an example?
https://developer.apple.com/design/human-interface-guidelines/watchos/interaction/modality/
The following code basically recreates the example screenshot from above:
import SwiftUI
struct ModalView: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
Image(systemName: "headphones")
.font(.system(size: 30))
Text("To play audio, connect Bluetooth headphones to your Apple Watch.")
.font(.footnote)
.multilineTextAlignment(.center)
}
.opacity(0.8)
.padding(10)
Spacer()
Button("Connect a Device") {
isPresented.toggle()
}.padding(.horizontal)
}
}
struct TestView: View {
#State private var isPresentingModalView = false
var body: some View {
Button("Connect") {
isPresentingModalView.toggle()
}
.fullScreenCover(isPresented: $isPresentingModalView) {
ModalView(isPresented: $isPresentingModalView)
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Result:
See docs:
https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:)

SwiftUI Let View disappear automatically

I have a view that is triggered by a button touch. It appears nicely, all good. Now I want the View to disappear automatically again after a few seconds.
The view should disappear automatically without having to hit the button again.
Below my test project
import SwiftUI
struct ContentView: View {
#State private var presentClipboardView = false
#State private var scale: CGFloat = 1.0
var body: some View {
VStack{
Button(action: {
let pasteboard = UIPasteboard()
pasteboard.string = "http://I_AM_A_URL.com"
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}, label: {
HStack {
Image(systemName: "list.dash")
.padding(.trailing)
VStack(alignment: .leading) {
Text("Open URL")
.font(.headline)
}
Spacer()
}
}
)
if(self.presentClipboardView){
LabelView()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct LabelView: View {
var body: some View {
Text("URL copied to clipboard!")
.padding(10)
.font(.title)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
}
}
Try this on LabelView()
LabelView().onAppear {
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}
}
lets try
import SwiftUI
struct ContentView: View {
#State var flag = false
let time = 3.0
var body: some View {
VStack {
if flag {
DetailView(flag: $flag, showTime: time)
}
Button(action: {
self.flag.toggle()
}) {
Text("show for \(time.description) seconds")
}.disabled(flag)
}
}
}
struct DetailView: View {
#Binding var flag: Bool
let showTime: Double
var body: some View {
Text("Welcome").font(.largeTitle).foregroundColor(Color.orange)
.onAppear {
let _delay = RunLoop.SchedulerTimeType(.init(timeIntervalSinceNow: self.showTime))
RunLoop.main.schedule(after: _delay) {
self.flag.toggle()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI - Animation in view stops when scrolling the list

Considering the following code, why the animations in the views that are initialized without the n property stops when you scroll the list?
Tested on Xcode 11.3 (11C29) with a new default project on device and simulator.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
List(1...50, id: \.self) { n in
HStack {
KeepRolling()
Spacer()
KeepRolling(n: n)
}
}
}
}
}
struct KeepRolling: View {
#State var isAnimating = false
var n: Int? = nil
var body: some View {
Rectangle()
.frame(width: 50, height: 50)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0))
.onAppear {
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
self.isAnimating = true
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
IMO it is due to caching/reuse in List. For List all the values of KeepRolling() is the same, so .onAppear is not always re-called.
If to make every such view unique, say using .id as below, all works (tested with Xcode 11.2)
KeepRolling().id(UUID().uuidString)

How can you switch views without having a navigationView or an popover?

I am trying to change a view without having something over it like when you used segue in swift. But the only solution I came up with is to have a navigation bar navigationBar or a popover.
struct view1: View {
var body: some View{
Button(action: {
// go to view2``
}) {
Text("press")
}
}
}
struct view2: View {
var body: some View{
Text("yeay")
}
}
If you just want to hide the navigation bar it's easy:
import SwiftUI
struct View2: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("POP")
}
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: View2()) {
Text("PUSH")
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you, instead, want to get rid of the NavigationView and NavigationLink views you have to implement your own custom navigation. It's a little more complicated. The following is just a simple example of a push/pop transition between two views.
import SwiftUI
struct View2: View {
#Binding var push: Bool
var body: some View {
ZStack {
Color.yellow
Button(action: {
withAnimation(.easeOut(duration: 0.3)) {
self.push.toggle()
}
}) {
Text("POP")
}
}
.edgesIgnoringSafeArea(.all)
}
}
struct View1: View {
#Binding var push: Bool
var body: some View {
ZStack {
Color.green
Button(action: {
withAnimation(.easeOut(duration: 0.3)) {
self.push.toggle()
}
}) {
Text("PUSH")
}
}
.edgesIgnoringSafeArea(.all)
}
}
struct ContentView: View {
#State private var push = false
var body: some View {
ZStack {
if !push {
View1(push: $push)
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .leading)))
}
if push {
View2(push: $push)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .trailing)))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Anyone coming to this later might find this to be of interest; in short, shove a hunk of data into #environment, tickle that with a button push in whatever view you want, and since it's at the very top of the overall application stack, it forces a redraw, which acts like a full view navigation, without the potential lost memory and orphaned or lost objects of the whole push/pop navigation view silliness.
It's still a little more "single page app"-ey than I'd like, but since SwiftUI is so crippled in its navigation thoroughness, it'll do nicely.
Not my site, not my link, not my tutorial, and it's buried way down in the list of hits when searching, which is a shame; this is the closest to what many are looking for. IMO, this should be baked into SwiftUI as a first class operation, and made less workaround-ey.
https://blckbirds.com/post/how-to-navigate-between-views-in-swiftui-by-using-an-environmentobject/
You can also do this completely without NavigationView. Take a look at the following example:
struct MainView: View
{
#State private var showView = "LoginView"
var body: some View
{
switch showView
{
case "LoginView":
Text("Please login.")
Button("Login")
{
showView = "NormalView"
}
case "NormalView":
Text("This is youre NormalView!")
Button("Next view")
{
showView = "NextView"
}
case "NextView":
Text("This is the NextView")
Button("Back")
{
showView = "NormalView"
}
default:
Text("Default") // you should never reach this
}
}
}
Perhaps not the best code practice, but it solves your problem.
I think this not best way but it's easy
struct ContentView: View {
#State var gotoDetail3:Bool = false
var body: some View {
NavigationView{
ZStack{
VStack {
// Normal NavigationLink
NavigationLink {
Text("Detail......")
} label: {
Text("goto..")
}
//use for change state
Button {
gotoDetail3.toggle()
} label: {
Text("goto33333")
}
}// End VStack
// Hide Navigation Link
NavigationLink(
LocalizedStringKey("123"), destination: Text("Subsequent View"),
isActive: $gotoDetail3)
.hidden()
}
}
}
}