Animated dots relocate after device rotation - ios

I've created a button with three animating dots using SwiftUI. When the screen geometry changes somehow (device rotation, keyboard open, etc.) the animation gets messed up. See the attached video. How can I get the circles to stay in their position relative to the button?
This is my code:
struct ContentView: View {
var body: some View {
Button(action: {}, label: {
AnimatedDot().frame(minWidth: 200, maxWidth: .infinity, minHeight: 20, maxHeight: 20)
}).foregroundColor(Color.blue)
.padding()
.background(Color.gray)
.cornerRadius(10.0)
.frame(minWidth: 0, maxWidth: 350)
}
}
struct AnimatedDot: View {
#State private var shouldAnimate = false
var body: some View {
HStack {
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever())
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever().delay(0.3))
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever().delay(0.6))
}
.onAppear {
self.shouldAnimate = true
}
}
}

Here is fixed body - join animation to state value.
Tested with Xcode 12.1 / iOS 14.1
var body: some View {
HStack {
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever(), value: shouldAnimate)
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever().delay(0.3), value: shouldAnimate)
Circle()
.fill(Color.white)
.frame(width: 15, height: 15)
.scaleEffect(shouldAnimate ? 1.0 : 0.5)
.animation(Animation.easeInOut(duration: 0.3).repeatForever().delay(0.6), value: shouldAnimate)
}
.onAppear {
self.shouldAnimate = true
}
}

Related

Issue with automatic timed scroll view in SwiftUI [duplicate]

This question already has answers here:
How to scroll List programmatically in SwiftUI?
(10 answers)
Closed 8 months ago.
I am trying to make an automatic custom paged scroll view but I have run into an issue. I specified in my code for every 5 seconds to add an increment of 1 and go to the next page and organized text to each card with Identifiable. This is not happening in the preview. Instead, it is refusing to move and it is wanting to move only to the first card. When I put a fixed string value and remove the array that I have made, it works. I have tried to use other alternatives like an enum and a fixed array embedded in the view but none of that worked. Please review my code below…
struct TestView: View {
private var amount = 1
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
#State private var currentIndex = 0
var item: Item = items[0]
var body: some View {
ZStack {
ScrollViewReader { scrollProxy in
SnappingScrollView(.horizontal, decelerationRate: .fast, showsIndicators: false) {
ForEach(items) { item in
ForEach(0..<amount) { selection in
GeometryReader { geometry in
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.2))
.blur(radius: 7)
.frame(maxWidth: 350, maxHeight: 300, alignment: .center)
.cornerRadius(16)
.shadow(color: Color.black.opacity(0.2), radius: 20, x: 0, y: 0)
.offset(x: 15, y: 50)
.rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 30) / -20), axis: (x: 0, y: 1.0, z: 0))
Rectangle()
.stroke(lineWidth: 50)
.foregroundColor(.gray)
.blur(radius: 100)
.frame(maxWidth: 350, maxHeight: 300, alignment: .center)
.cornerRadius(16)
.shadow(color: Color.black.opacity(0.3), radius: 1)
.offset(x: 15, y: 50)
.rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 30) / -20), axis: (x: 0, y: 1.0, z: 0))
VStack {
Text(item.title)
.font(.title2)
.fontWeight(.bold)
.padding(.bottom, 10)
Text(item.description)
.font(.system(size: 16))
Image(item.image)
.resizable()
.frame(width: 90, height: 90)
.cornerRadius(25)
.padding(.bottom, -30)
.padding(.top)
Text("\(selection)")
.opacity(0)
}
.frame(width: 300)
.multilineTextAlignment(.center)
.offset(y: 20)
.rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 30) / -20), axis: (x: 0, y: 1.0, z: 0))
.padding(.leading, 25)
.padding(.top, 70)
}
}
.frame(width: 400, height: 500)
.offset(x: 12)
.id(selection)
.scrollSnappingAnchor(.bounds)
}
}
}
.onReceive(timer) { _ in
currentIndex = currentIndex < amount-1 ? currentIndex + 1 : 0
withAnimation {
scrollProxy.scrollTo(currentIndex) // scroll to next .id
}
}
}
}
}
}
The Identifiable array
struct Item: Identifiable {
var id = UUID()
var title: String
var description: String
var image: String
}
var items = [
Item(title: "Welcome to Mathematically!", description: "It's a pleasure to have you. From creating a playground to competing in the daily challenges, there will be wonderful days of math waiting for you.", image: "Mathematically"),
Item(title: "An award winning app.", description: "Mathematically was submitted to the WWDC22 Swift Student Challenge and was accepted among the 350 accepted apps world-wide.", image: "WWDC22"),
Item(title: "Need a Walkthrough?", description: "No need to be intimidated. Take the walkthrough if you need to familiarize yourself about the game.", image: "Arrows"),
Item(title: "Mathematically+", description: "The ultimate premium Mathematically experience, ad-free.", image: "Mathematically+"),
]
You're almost there .. you just don't do anything with your currentIndex when it changes. Introduce a ScrollViewReader and an .id so you can scroll to the respective position in your view:
struct ContentView: View {
private let amount = 4
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
#State private var currentIndex = 0
var body: some View {
ScrollViewReader { scrollProxy in // needed to scroll programmatically
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(0..<amount) { item in
GeometryReader { geometry in
ZStack {
Rectangle()
.foregroundColor(.gray)
.frame(maxWidth: 350, maxHeight: 300, alignment: .center)
.cornerRadius(16)
.offset(x: 15, y: 50)
.rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 30) / -20), axis: (x: 0, y: 1.0, z: 0))
VStack {
Text("Hello World!")
.font(.title2)
.bold()
.padding(.bottom, 10)
Text("Item \(item)")
}
.frame(width: 300)
.rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 30) / -20), axis: (x: 0, y: 1.0, z: 0))
.padding(.leading, 25)
.padding(.top, 70)
}
}
.frame(width: 400, height: 500)
.offset(x: 10)
.id(item) // define id here
}
}
}
.onReceive(timer) { _ in
currentIndex = currentIndex < amount-1 ? currentIndex + 1 : 0
withAnimation {
scrollProxy.scrollTo(currentIndex) // scroll to next .id
}
}
}
}
}

How can I give a background Image to my View in SwiftUI

I'm new to SwiftUI and iOS and I'm having trouble getting my VStack to stack items properly. Mainly, it looks like the image on the top left is too big. Here's a screenshot of the size of the image in my layout:
This is what it should look like:
This is the SwiftUI code:
#State private var email: String = ""
#State private var password: String = ""
#State private var isEditing = false
var body: some View {
return VStack(content: {
Image("UIBubble")
.resizable()
.frame(width: 217, height: 184, alignment: .leading)
.position(x: 108, y: 92);
Text("PET SUPPORT").font(Font.custom("Permanent Marker", size: 36))
.foregroundColor(Color.petSupportText)
.padding()
.frame(width: .infinity, height: 0, alignment: .center);
Text("EMAIL").font(Font.custom("Permanent Marker", size: 18))
.padding(.top, 20)
.padding(.horizontal, 0)
.frame(width: .infinity, height: 0, alignment: .leading);
TextField("Email", text: $email) {
isEditing in self.isEditing = isEditing
}
.autocapitalization(.none)
.disableAutocorrection(true)
.padding(.horizontal, 30)
.padding(.top, 20);
Divider()
.padding(.horizontal, 30)
.foregroundColor(.black);
Text("PASSWORD").font(Font.custom("Permanent Marker", size: 18)).padding(.top, 50).padding(.horizontal, 0).frame(width: .infinity, height: 20, alignment: .leading);
TextField("Password", text: $password) {
isEditing in self.isEditing = isEditing
}
.autocapitalization(.none)
.disableAutocorrection(true)
.padding(.horizontal, 30)
.padding(.top, 20)
Divider().padding(.horizontal, 30).foregroundColor(.black);
})
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
Image giving me trouble:
The image itself is 217x184.
You should use ZStack for this work or send the Image to background like in code:
Also I made some code for you to make you free to use that file Image and you get same and much more better result with system shape. you can change the Color, shape, size, number of bubble and every thing . .
with usage of contrast: make the Color like your Color
import SwiftUI
struct ContentView: View {
#State private var email: String = ""
#State private var password: String = ""
#State private var showPassword = false
var body: some View {
return VStack(content: {
Spacer()
.frame(height: 100)
Text("PET SUPPORT")
.font(Font.custom("Permanent Marker", size: 36))
.padding()
Group {
HStack {
Text("EMAIL")
.font(Font.custom("Permanent Marker", size: 18))
.padding(.top, 20)
Spacer()
}
TextField("Email", text: $email)
.autocapitalization(.none)
.disableAutocorrection(true)
.keyboardType(.emailAddress) // <<: Here: A type optimized for multiple email address entry (shows space # . prominently).
.padding(.top, 20)
Divider()
.foregroundColor(.black)
}
Group {
HStack {
Text("PASSWORD")
.font(Font.custom("Permanent Marker", size: 18))
.padding(.top, 50)
Spacer()
}
ZStack {
if showPassword {
TextField("Password", text: $password)
}
else {
SecureField("Password", text: $password)
}
}
.frame(height: 20)
.overlay(Image(systemName: showPassword ? "eye.slash" : "eye").onTapGesture { showPassword.toggle() }, alignment: .trailing)
.autocapitalization(.none)
.disableAutocorrection(true)
.padding(.top, 20)
Divider()
.foregroundColor(.black)
}
Group {
Button("Sign Up") {}
.padding()
Button("Login") {}
.padding()
}
Spacer()
})
.padding(.horizontal, 30)
.background(bubble, alignment: .topLeading)
.background(bubble.rotationEffect(Angle(degrees: 180)), alignment: .bottomTrailing)
.ignoresSafeArea()
.statusBar(hidden: true)
}
var bubble: some View { // <<: you can put this part out and use update code for this part
Image("UIBubble")
.resizable()
.scaledToFit()
.frame(width: 217, height: 184, alignment: .leading)
}
}
Update: you can replace that file Image with high quality system shape like this code!
var bubble: some View {
ZStack {
Circle()
.fill(Color(UIColor.systemTeal).opacity(0.2)) // <<: Here: opacity = 0.2
.frame(width: 300, height: 300, alignment: .center)
.offset(x: -100, y: -180)
Circle()
.fill(Color(UIColor.systemTeal).opacity(0.2)) // <<: Here: opacity = 0.2
.frame(width: 300, height: 300, alignment: .center)
.offset(x: -180, y: -100)
}
.contrast(3.0) // <<: Here: This make your Color POP out!
}
Update 2: with this down code your Circle/bubble came to live and they move with smooth animation.
#State private var startAnimation: Bool = false
var bubble: some View {
ZStack {
Circle()
.fill(Color(UIColor.systemTeal).opacity(0.2)) // <<: Here: opacity = 0.2
.frame(width: 300, height: 300, alignment: .center)
.offset(x: startAnimation ? -110 : -100, y: startAnimation ? -180 : -150)
Circle()
.fill(Color(UIColor.systemTeal).opacity(0.2)) // <<: Here: opacity = 0.2
.frame(width: 300, height: 300, alignment: .center)
.offset(x: startAnimation ? -180 : -150, y: startAnimation ? -90 : -100)
}
.contrast(3.0) // <<: Here: This make your Color POP out!
.onAppear() { startAnimation = true }
.animation(Animation.easeInOut(duration: 3.0).repeatForever(autoreverses: true))
}
Seems like you can just use background(alignment:content:) modifier
So, add this background modifiers to your view:
VStack {
...
}
.background(alignment: .topLeading) {
Image("UIBubble")
}
.background(alignment: .bottomTrailing) {
Image("UIBubble2"),
}
In my opinion this solution is the most swifty

Home page view background color in not on full screen in iPhone 11 Simulator

I am trying to build an app in SwiftUI and facing 1 challenge (Xcode Version 11.5) -
While running app on iPhone 11 simulator, background color is not coming on entire screen, bottom part of screen is still white however while running it on iPhone 8 simulator, it works fine. Not sure if it is simulator issue or code issue. I tried to add Spacer, change VStack, HStack but it did not work.
struct HomePageView: View {
#State var size = UIScreen.main.bounds.width / 1.6
var body: some View {
GeometryReader{geometry in
VStack {
HStack {
ZStack{
NavigationView{
ZStack {
ScrollView(.vertical, showsIndicators: false) {
VStack {
View1()
}.frame( maxWidth: .infinity)
}
.navigationBarItems(leading: Button(action: {
self.size = 10
}, label: {
Image("menu")
.resizable()
.frame(width: 30, height: 30)
}).foregroundColor(.appHeadingColor), trailing:
Button(action: {
print("profile is pressed")
}) {
HStack {
NavigationLink(destination: ProfileView()) {
LinearGradient.lairHorizontalDark
.frame(width: 30, height: 30)
.mask(
Image(systemName: "person.crop.circle")
.resizable()
.scaledToFit()
)
}
}
}
).navigationBarTitle("Home", displayMode: .inline)
}
}
HStack{
menu(size: self.$size)
.cornerRadius(20)
.padding(.leading, -self.size)
.offset(x: -self.size)
Spacer().background(Color.lairBackgroundGray)
}
//Spacer()
}.animation(.spring()).background(Color.lairBackgroundGray)
//Spacer()
}.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom)
}.frame(height: geometry.size.height).background(Color.lairBackgroundGray)
}//.background(Color.lairBackgroundGray.edgesIgnoringSafeArea(.all))
}
}
Below is my another view which basically get painted on screen as part of home view. Please forgive me to put so much code here but wanted to make sure if it is not because of View1 -
struct View1: View {
#State var index = 0
var body: some View{
// ScrollView {
GeometryReader { geometry in
VStack{
HStack{
VStack {
ZStack{
Circle()
.trim(from: 0, to: 1)
.stroke(Color.lairDarkGray.opacity(0.09), style: StrokeStyle(lineWidth: 34, lineCap: .round))
.frame(width: 80, height: 80)
Circle()
.trim(from: 0, to: 0.5)
.stroke(LinearGradient(gradient: Gradient(colors: [.buttonGradientStartColor, .buttonGradientEndColor]), startPoint: UnitPoint(x: -0.2, y: 0.5), endPoint: .bottomTrailing), style: StrokeStyle(lineWidth: 34, lineCap: .round))
.frame(width: 80 , height: 80)
.rotationEffect(.init(degrees: -90))
Text("15")
.font(.system(size:30))
.fontWeight(.bold)
}.padding()
Text("Day(s)")
.foregroundColor(Color.black.opacity(0.8))
}.frame(height: 100)
VStack(alignment: .leading, spacing: 12){
HStack {
Image("1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 170, height: 170)
}
.background(Color.lairBackgroundGray)
//.padding(.bottom, 5)
}
.padding(.leading, 20)
Spacer(minLength: 0)
}
.padding(.horizontal, 20)
}//.frame(height: geometry.size.height)
.background(Color.lairBackgroundGray.edgesIgnoringSafeArea(.all))
}
//}
}
}[![enter image description here][1]][1]
You just need to add
.edgesIgnoringSafeArea(.all)
to the view that you want to go fullscreen
After recreating entire view and removing some unnecessary view, i was able to resolve this iissue.

Rectangle in ZStack changing SwiftUI animation

I am trying to transition between two states with SwiftUI.
I have reduced this to a simple example
struct Test2View: View {
#State var isLoading: Bool = true
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color. secondary)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
}
}
extension AnyTransition {
static func moveAndFade(delay: TimeInterval = 0) -> AnyTransition {
let insertion = AnyTransition.offset(x: 0, y: 15)
.combined(with: .opacity)
let removal = AnyTransition.offset(x: 20, y: 20)
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
This works the way I expected without the RoundedRectangle:
However, as soon as I add the RoundRectangle I lose the removal animation (unless I interrupt the animation, then you can see the animation I was expecting):
Any idea why the RoundedRectangle messes with the animation? I even tried to add .transition(.identity) without any success.
I can't tell the exact reason why the animation changes, but I have found the cause. It's something to do with the size of the frame.
Your version of Test2View's view body:
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color.white)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}
.border(Color.red)
.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
Fixed version:
ZStack {
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.white)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
)
.border(Color.green)
.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
Basically, I used .background instead of creating a VStack.
Comparison images:
The border color is there just to indicate the frame size. You can remove this!

SwiftUI won't align ZStacks

I'm currently trying to handle SwiftUI by following a tutorial, but somehow I can't solve one issue:
I created another View, namely my HomeView.swift - this file contains the following code:
import SwiftUI
struct Home: View {
var menu = menuData
#State var show = false
var body: some View {
ZStack {
ZStack(alignment: .topLeading) {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}
ZStack(alignment: .topTrailing) {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}
MenuView(show: $show)
}
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
struct MenuRow: View {
var text: String?
var image: String?
var body: some View {
HStack {
Image(systemName: image ?? "")
.foregroundColor(Color("third"))
.frame(width: 32, height: 32, alignment: .trailing)
Text(text ?? "")
.font(Font.custom("Helvetica Now Display Bold", size: 15))
.foregroundColor(Color("primary"))
Spacer()
}
}
}
struct Menu: Identifiable {
var id = UUID()
var title: String
var icon: String
}
let menuData = [
Menu(title: "My Account", icon: "person.crop.circle.fill"),
Menu(title: "Reservations", icon: "house.fill"),
Menu(title: "Sign Out", icon: "arrow.uturn.down")
]
struct MenuView: View {
var menu = menuData
#Binding var show: Bool
var body: some View {
VStack(alignment: .leading, spacing: 20) {
ForEach(menu) { item in
MenuRow(text: item.title, image: item.icon)
}
Spacer()
}
.padding(.top, 20)
.padding(30)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.white)
.cornerRadius(30)
.padding(.trailing, 60)
.shadow(radius: 20)
.rotation3DEffect(Angle(degrees: show ? 0 : 60), axis: (x: 0, y: 10, z: 0))
.animation(.default)
.offset(x: show ? 0 : -UIScreen.main.bounds.width)
.onTapGesture {
self.show.toggle()
}
}
}
As you can see, right in the beginning, inside of my Home struct, I tried to align two ZStacks - one .topLeading and one .topTrailing. Reading the docs, this should change its position, but somehow it doesn't. Both stack stay centered.
BTW I haven't particularly touched ContenView.swift yet.
Actually, for either inner ZStack, you need to set frames. This can make them reach edges.
ZStack{
ZStack{
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
ZStack{
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
MenuView(show: $show)
}
struct Home: View {
var menu = menuData
#State var show = false
var body: some View {
ZStack {
VStack {
HStack {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
}
Spacer()
}
MenuView(show: $show)
}
}
}
Is this the layout that you are looking for? With VStack and HStack you can align the views to the top and on both edges

Resources