How I can use animation compilation or chain animation in SwiftUI? - swiftui

I have a Rectangle which start moving from point A to middle and then from middle to B with action of 2 Buttons or 2 moves(move1 and move2),
I need to read the end of animation move1 to start move2, in UIKit we could use animation compilation, I want use or access the same possibility in SwiftUI.
My Goal: Rectangle animate those 2 animation in chain mode, first chain move1 and then second chain move2.
PS: I know about DispatchQueue, I prefer to use chain animation!
struct ContentView: View {
#State var move1: Bool = false
#State var move2: Bool = false
var body: some View {
ZStack
{
VStack
{
VStack
{
Rectangle().fill(Color.green).frame(width: 200, height: 200, alignment: .center)
.cornerRadius((move1 == false && move2 == false) ? 0 : ((move1 == true && move2 == true) ? 0 : 50))
}
.offset(y: move1 ? (UIScreen.main.bounds.height-200)/2 : 0)
.offset(y: (move1 == true && move2 == true) ? (UIScreen.main.bounds.height-200)/2 : 0)
.animation( Animation.easeInOut(duration: 1) )
Spacer()
}
.ignoresSafeArea()
VStack
{
Image(systemName: "a.circle").font(Font.title.bold()).offset(y: 100)
Spacer()
HStack
{
Spacer()
Button("Move1") { move1.toggle()}.font(Font.title.bold()).padding().overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.purple, lineWidth: 2))
Spacer()
Button("Move2") { move2.toggle()}.font(Font.title.bold()).padding().overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.purple, lineWidth: 2))
Spacer()
}
Spacer()
Image(systemName: "b.circle").font(Font.title.bold()).offset(y: -100)
}
.ignoresSafeArea()
}
}
}

Related

resizable header in SwiftUI

I was trying to make resizable header but it breaks.
I am trying to clone twitter profile,
I think logic is right but can I know why this one is not working?
I made HStack and try to hide it but when I scroll back it can't come back.
Tried with GeometryReader
Please help me, thanks
import SwiftUI
struct ProfileView: View {
// change views
#State var isHide = false
#State private var selectionFilter: TweetFilterViewModel = .tweets
#Namespace var animation
var body: some View {
VStack(alignment: .leading) {
//hiding
if isHide == false {
headerView
actionButtons
userInfoDetails
}
tweetFilterBar
.padding(0)
ScrollView(showsIndicators: false) {
LazyVStack {
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < height && !isHide {
DispatchQueue.main.async {
withAnimation {
isHide = true
}
}
}
if yAxis > 0 && isHide {
DispatchQueue.main.async {
withAnimation {
isHide = false
}
}
}
return AnyView(
Text("")
.frame(width: 0, height: 0)
)
}
.frame(width: 0, height: 0)
ForEach(0...9, id: \.self) { _ in
TweetRowView()
}
}
}
Spacer()
}
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
extension ProfileView {
var headerView: some View {
ZStack(alignment: .bottomLeading) {
Color(.systemBlue)
.ignoresSafeArea()
VStack {
Button {
} label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 16)
.foregroundColor(.white)
.position(x: 30, y: 12)
}
}
Circle()
.frame(width: 72, height: 72)
.offset(x: 16, y: 24)
}.frame(height: 96)
}
var actionButtons: some View {
HStack(spacing: 12){
Spacer()
Image(systemName: "bell.badge")
.font(.title3)
.padding(6)
.overlay(Circle().stroke(Color.gray, lineWidth: 0.75))
Button {
} label: {
Text("Edit Profile")
.font(.subheadline).bold()
.accentColor(.black)
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray, lineWidth: 0.75))
}
}
.padding(.trailing)
}
var userInfoDetails: some View {
VStack(alignment: .leading) {
HStack(spacing: 4) {
Text("Heath Legdet")
.font(.title2).bold()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue))
}
.padding(.bottom, 2)
Text("#joker")
.font(.subheadline)
.foregroundColor(.gray)
Text("Your mom`s favorite villain")
.font(.subheadline)
.padding(.vertical)
HStack(spacing: 24) {
Label("Gothem.NY", systemImage: "mappin.and.ellipse")
Label("www.thejoker.com", systemImage: "link")
}
.font(.caption)
.foregroundColor(.gray)
HStack(spacing: 24) {
HStack {
Text("807")
.font(.subheadline)
.bold()
Text("following")
.font(.caption)
.foregroundColor(.gray)
}
HStack {
Text("200")
.font(.subheadline)
.bold()
Text("followers")
.font(.caption)
.foregroundColor(.gray)
}
}
.padding(.vertical)
}
.padding(.horizontal)
}
var tweetFilterBar: some View {
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectionFilter == item ? .semibold : .regular)
.foregroundColor(selectionFilter == item ? .black : .gray)
ZStack {
Capsule()
.fill(Color(.clear))
.frame(height: 3)
if selectionFilter == item {
Capsule()
.fill(Color(.systemBlue))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
}
}
}
.onTapGesture {
withAnimation(.easeInOut) {
self.selectionFilter = item
}
}
}
}
}
}
While the animation between show and hide is running, the GeometryReader is still calculating values – which lets the view jump between show and hide – and gridlock.
You can introduce a new #State var isInTransition = false that checks if a show/hide animation is in progress and check for that. You set it to true at the beginning of the animation and to false 0.5 secs later.
Also I believe the switch height is not exactly 1/2 of the screen size.
So add a new state var:
#State var isInTransition = false
and in GeometryReader add:
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < 350 && !isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = true
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
} else if yAxis > 0 && isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = false
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
}
return AnyView(
// displaying values for test purpose
Text("\(yAxis) - \(isInTransition ? "true" : "false")").foregroundColor(.red)
// .frame(width: 0, height: 0)
)
}

Do not want NavigationView to popup after some tap of the button

I am creating an app to give reward stickers for kids. When 10 stickers are collected, I want all stickers are removed from the main view and the image will be shown before removing everything. This image is shown at the 11th click of the button but navigation view to select stickers are also shown on top of this image, which I do not want to be popped up.
Here is my code...
NavigationView {
ZStack {
Image("Greenbase")
.resizable()
.frame(width: self.screenWidth, height: self.screenWidth)
ZStack {
ForEach($stickers) { $sticker in
VStack {
Image(sticker.name)
.resizable()
.frame(width: self.screenWidth*0.2, height: self.screenWidth*0.2)
.offset(x: self.selectedSticker == sticker ? sticker.offset.width + self.dragOffset.width : sticker.offset.width,
y: self.selectedSticker == sticker ? sticker.offset.height + self.dragOffset.height : sticker.offset.height)
.gesture(
DragGesture()
.updating(self.$dragOffset, body: { (value, state, transaction) in
if nil == self.selectedSticker {
self.selectedSticker = sticker
}
state = value.translation
})
.onEnded { value in
sticker.offset.height += value.translation.height + dragOffset.height
sticker.offset.width += value.translation.width + dragOffset.width
self.selectedSticker = nil
}
)
}//vstack
.sheet(isPresented: $isAddingSticker, onDismiss: addSticker) {
StampView(sticker: $selectedSticker)
}//foreach
if counter < 11 {
Button {
isAddingSticker = true
print("button pressed: \(counter)")
counter += 1
}label: {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color(red: 55/255, green: 266/255, blue: 213/255))
.frame(width: 300, height: 40, alignment: .center)
.overlay(
Text(" ステッカーをはろう!")
.foregroundColor(Color(red: 41/255, green: 52/255, blue: 98/255))
.font(.title2))
}//button label
}else {
Button{
isAddingSticker = false
counter = 0
removeSticker(at: 1)
}label: {
Image("WellDone")
.resizable()
.frame(width: self.screenWidth*0.5, height: self.screenWidth*0.5)
.scaleEffect(2.0)
.rotationEffect(Angle(degrees: 360 ))
.animation(.easeInOut(duration: 3.0))
}//button label
} //else
}//Hstack
.disabled(isEditing)
}
}//zstack stamps
}//zstac w bc image
}//body
func removeSticker(at index: Int) {
self.stickers.removeAll()//< -- Here
}
func addSticker() {
guard let name = selectedSticker else { return }
withAnimation {
stickers.insert(name, at: 0)
}
}
}//struct
I am still super new to Swift... the question should be very basic but hopefully can find the answer.
I don't think I understand the question correctly, but you can hide the navigation with this modifier at the end of the navigation view.
NavigationView {
...
}.navigationBarHidden(true)

Error creating a custom PageView in SwiftUI 2

I am working on an app that requires 6 items in a PageView, xcode was acting up so i came on Stack Overflow and found pawello2222's way, found it really helpful but I still need to connect it to the horizontal links on top. here is the code, thanks a million.
Original solution: How can I implement PageView in SwiftUI?
Below is my code and what I want to achieve, swiping left or right works, but clicking on the links does not change the view
struct TestingView: View {
#State var selection = 0
var body: some View {
VStack {
HStack(spacing: 10) {
VStack {
Text("Link 1")
.foregroundColor(self.selection == 0 ? Color.blue : Color("Silver").opacity(0.7))
.font(.caption)
.fontWeight(.bold)
.clipShape(Rectangle())
.onTapGesture {
withAnimation(.default) {
self.selection = 0
}
}
}
Spacer(minLength: 0)
VStack {
Text("Link 2")
.foregroundColor(self.selection == 1 ? Color.blue : Color("Silver").opacity(0.7))
.font(.caption)
.fontWeight(.bold)
.clipShape(Rectangle())
.onTapGesture {
withAnimation(.default) {
self.selection = 1
}
}
}
Spacer(minLength: 0)
VStack {
Text("Link 3")
.foregroundColor(self.selection == 2 ? Color.blue : Color("Silver").opacity(0.7))
.font(.caption)
.fontWeight(.bold)
.clipShape(Rectangle())
.onTapGesture {
withAnimation(.default) {
self.selection = 2
}
}
}
}
PageView(selection: $selection, indexDisplayMode: .never, indexBackgroundDisplayMode: .never) {
VStack {
FirstView()
}
.tag(0)
VStack {
SecondView()
}
.tag(1)
VStack {
ThirdView()
}
.tag(2)
}
}
}
}

Show hint view in LazyVGrid in SwiftUI

I have lots of button in a LazyVGrid in a ScrollView. I am trying to show a hint view just top of the button I clicked (as like keyboard popup). I don't know how do I catch the position of a ScrollView button. Besides need help to select suitable gesture to complete the task.
Graphical representation...
Here is my code:
struct ShowHint: View {
#State var isPressed: Bool = false
var columns: [GridItem] = Array(repeating: .init(.flexible()), count: 5)
var body: some View {
ZStack{
if isPressed {
ShowOnTopOfButton().zIndex(1)
}
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 30) {
ForEach(0..<500) { i in
Text("\(i)")
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.background(Color.red.opacity( isPressed ? 0.5 : 0.9))
.gesture(TapGesture()
//.onStart { _ in isPressed = true } //but there is no property like this!
.onEnded { _ in isPressed = !isPressed }
)
}
}
}
.padding(.top, 50)
.padding(.horizontal, 10)
}
}
}
struct ShowOnTopOfButton: View {
var theS: String = "A"
var body: some View {
VStack {
Text("\(theS)")
.padding(20)
.background(Color.blue)
}
}
}
Here is possible solution - the idea is to show hint view as overlay of tapped element.
Tested with Xcode 12 / iOS 14
struct ShowHint: View {
#State var pressed: Int = -1
var columns: [GridItem] = Array(repeating: .init(.flexible()), count: 5)
var body: some View {
ZStack{
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 30) {
ForEach(0..<500) { i in
Text("\(i)")
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.background(Color.red.opacity( pressed == i ? 0.5 : 0.9))
.gesture(TapGesture()
.onEnded { _ in pressed = pressed == i ? -1 : i }
)
.overlay(Group {
if pressed == i {
ShowOnTopOfButton()
.allowsHitTesting(false)
}}
)
}
}
}
.padding(.top, 50)
.padding(.horizontal, 10)
}
}
}

SwiftUI Animation: How to Move THEN Show?

Example
Here is a menu with three items:
This is fine. But what I would like to achieve is:
Menu expands
Then the three images fade-in AFTER the menu is done expanding
I thought maybe adding a 2nd, delayed animation for the opacity might work but instead, it looks like all animation (movement and opacity) gets delayed:
Here is the code:
struct SequenceAnimation_SOQuestion: View {
#State private var show = false
var body: some View {
HStack(spacing: 40) {
Group {
Image(systemName: "pencil")
Image(systemName: "scribble")
Image(systemName: "lasso")
}
.opacity(show ? 1 : 0)
.animation(Animation.default.delay(0.5))
Button(action: { self.show.toggle() }) {
Image(systemName: "line.horizontal.3.decrease")
.rotationEffect(.degrees(-90))
}.offset(x: 10)
}
.padding(20)
.padding(.leading, 40)
.foregroundColor(.white)
.background(Capsule().fill(Color.blue))
.font(.largeTitle)
.offset(x: show ? -70 : -320)
.animation(.default)
}
}
You should add each animation separately, then you can have different animations for each one:
struct ContentView: View {
#State private var isMenuCollapsed = true
#State private var isItemsVisible = false
var body: some View {
HStack(spacing: 40) {
Group {
Image(systemName: "pencil")
Image(systemName: "scribble")
Image(systemName: "lasso")
}
.opacity(isItemsVisible ? 1 : 0)
Button(action: {
withAnimation(Animation.default) {
self.isMenuCollapsed.toggle()
}
withAnimation(Animation.default.delay(0.2)) {
self.isItemsVisible.toggle()
}
}) {
Image(systemName: "line.horizontal.3.decrease")
.rotationEffect(.degrees(-90))
}.offset(x: 10)
}
.padding(20)
.padding(.leading, 40)
.foregroundColor(.white)
.background(Capsule().fill(Color.blue))
.font(.largeTitle)
.offset(x: isMenuCollapsed ? -320 : -70)
}
}
I don't know exactly what kind of animation you want, but i worked something out which may be somewhere around your goal.
I changed the working of the animations. In your 2nd example your images were animation not right so that's what the following line fixes:
struct SequenceAnimation_SOQuestion: View {
#State private var show = false
var body: some View {
HStack(spacing: 40) {
Group {
Image(systemName: "pencil")
Image(systemName: "scribble")
Image(systemName: "lasso")
}
.opacity(show ? 1 : 0)
.animation(!self.show ? .default : Animation.default.delay(0.5))
Button(action: { self.show.toggle() }) {
Image(systemName: "line.horizontal.3.decrease")
.rotationEffect(.degrees(-90))
}.offset(x: 10)
}
.padding(20)
.padding(.leading, 40)
.foregroundColor(.white)
.background(Capsule().fill(Color.blue))
.font(.largeTitle)
.offset(x: show ? -70 : -320)
.animation(!self.show ? Animation.default.delay(0.5) : .default)
}
}
If you provide me a bit more specific information about your goal you're trying to achieve, I can maybe help you.