SwiftUI粒子吸入效果问题:需将四角吸入改为全向吸入
问题描述
我实现了两个按钮,一个用于向外发射粒子,一个用于向内吸入粒子。向外发射的按钮功能正常,但向内吸入的按钮仅能从-45°、45°、-135°、135°这四个角落吸入粒子,希望将其修改为从四周全方向吸入粒子。
原代码
struct AllVideoView: View { @State private var isLiked: [Bool] = [false, false] var body: some View { VStack(spacing: 40){ CustomButton(systemImage: "hand.thumbsup.fill", status: isLiked[0], activeTint: .green, inActiveTint: .gray) { isLiked[0].toggle() if isLiked[1] { isLiked[1] = false } } CustomButton(systemImage: "hand.thumbsdown.fill", status: isLiked[1], activeTint: .pink, inActiveTint: .gray) { isLiked[1].toggle() if isLiked[0] { isLiked[0] = false } } } } @ViewBuilder func CustomButton(systemImage: String, status: Bool, activeTint: Color, inActiveTint: Color, onTap: @escaping () -> ()) -> some View { Button(action: onTap) { Image(systemName: systemImage) .font(.title2) .particleEffect360( systemImage: systemImage, font: .body, status: status, activeTint: activeTint, inActiveTint: inActiveTint, direction: systemImage == "hand.thumbsup.fill" ? true : false ) .foregroundColor(status ? activeTint : inActiveTint) .padding(18) .background { Circle() .fill(status ? activeTint.opacity(0.6) : Color.blue.opacity(0.6)) } } } } struct Particle: Identifiable {//these particles are pushed from origin so start at 0,0 var id: UUID = .init() var randomX: CGFloat = 0 var randomY: CGFloat = 0 var scale: CGFloat = 1 var opacity: CGFloat = 1 mutating func reset() { randomX = 0 randomY = 0 scale = 1 opacity = 1 } } struct Negative_Particle360: Identifiable {//these particles are pulled in so start them far from origin var id: UUID = .init() var randomX: CGFloat = Bool.random() ? CGFloat.random(in: -100...(-80)) : CGFloat.random(in: 80...100) var randomY: CGFloat = Bool.random() ? CGFloat.random(in: -100...(-80)) : CGFloat.random(in: 80...100) var scale: CGFloat = 1 var opacity: CGFloat = 1 mutating func reset() { if Bool.random() { randomX = CGFloat.random(in: -100...(-80)) } else { randomX = CGFloat.random(in: 80...100) } if Bool.random() { randomY = CGFloat.random(in: -100...(-80)) } else { randomY = CGFloat.random(in: 80...100) } scale = 1 opacity = 1 } } extension View { @ViewBuilder func particleEffect360(systemImage: String, font: Font, status: Bool, activeTint: Color, inActiveTint: Color, direction: Bool) -> some View { self .modifier( ParticleModifier360(systemImage: systemImage, font: font, status: status, activeTint: activeTint, inActiveTint: inActiveTint, direction: direction) ) } } fileprivate struct ParticleModifier360: ViewModifier { var systemImage: String var font: Font var status: Bool var activeTint: Color var inActiveTint: Color var direction: Bool @State private var particles: [Particle] = [] @State private var negative_particles: [Negative_Particle360] = [] func body(content: Content) -> some View { content .overlay(alignment: .top) { ZStack { if direction { ForEach(particles) { particle in Image(systemName: systemImage) .font(font) .foregroundColor(status ? activeTint : inActiveTint) .scaleEffect(particle.scale) .offset(x: particle.randomX, y: particle.randomY) .opacity(particle.opacity) .opacity(status ? 1 : 0) .animation(.none, value: status) } } else { ForEach(negative_particles) { particle in Image(systemName: systemImage) .font(font) .foregroundColor(status ? activeTint : inActiveTint) .scaleEffect(particle.scale) .offset(x: particle.randomX, y: particle.randomY) .opacity(particle.opacity) .opacity(status ? 1 : 0) .animation(.none, value: status) } } } .onAppear { if direction { if particles.isEmpty { for _ in 1...15 { let particle = Particle() particles.append(particle) } } } else { if negative_particles.isEmpty { for _ in 1...15 { let particle = Negative_Particle360() negative_particles.append(particle) } } } } .onChange(of: status) { newValue in if !newValue { if direction { for index in particles.indices { particles[index].reset() } } else { for index in negative_particles.indices { negative_particles[index].reset() } } } else { if direction { //pushing particles out for index in particles.indices { let total: CGFloat = CGFloat(particles.count) let progress: CGFloat = CGFloat(index) / total let angle: CGFloat = progress * 2 * .pi let radius: CGFloat = 75 let centerX: CGFloat = 0 let centerY: CGFloat = 0 let randomX: CGFloat = radius * cos(angle) + centerX let randomY: CGFloat = radius * sin(angle) + centerY let randomScale: CGFloat = .random(in: 0.35...1) withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.7, blendDuration: 0.7)) { let extraRandomX: CGFloat = .random(in: -10...10) let extraRandomY: CGFloat = .random(in: 0...40) particles[index].randomX = randomX + extraRandomX particles[index].randomY = randomY + extraRandomY } withAnimation(.easeInOut(duration: 0.3)) { particles[index].scale = randomScale } withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.7, blendDuration: 0.7) .delay(0.25 + (Double(index) * 0.005))) { particles[index].scale = 0.001 } } } else {//pulling particles in logic for index in negative_particles.indices { let randomScale: CGFloat = .random(in: 0.8...1) withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.7, blendDuration: 0.7)) { let extraRandomX: CGFloat = .random(in: -5...5) let extraRandomY: CGFloat = .random(in: -5...5) negative_particles[index].randomX = extraRandomX negative_particles[index].randomY = extraRandomY } withAnimation(.easeInOut(duration: 0.3)) { negative_particles[index].scale = randomScale } withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.7, blendDuration: 0.7) .delay(0.25 + (Double(index) * 0.005))) { negative_particles[index].scale = 0.001 } } } } } } } }
解决方案
问题出在Negative_Particle360的初始化和reset方法中,当前逻辑仅让粒子从四个角落区域生成。我们可以通过极坐标随机角度的方式,让粒子从四周任意位置生成,实现全方向吸入效果。
修改后的Negative_Particle360结构体
struct Negative_Particle360: Identifiable { var id: UUID = .init() var randomX: CGFloat var randomY: CGFloat var scale: CGFloat = 1 var opacity: CGFloat = 1 // 初始化时生成随机圆周位置 init() { let radius: CGFloat = CGFloat.random(in: 80...100) let angle: CGFloat = .random(in: 0...2 * .pi) randomX = radius * cos(angle) randomY = radius * sin(angle) } mutating func reset() { let radius: CGFloat = CGFloat.random(in: 80...100) let angle: CGFloat = .random(in: 0...2 * .pi) randomX = radius * cos(angle) randomY = radius * sin(angle) scale = 1 opacity = 1 } }
逻辑说明
- 用
0...2 * .pi的随机角度覆盖360°全范围,确保粒子来自四周任意方向 - 通过三角函数
cos(angle)和sin(angle)计算圆周上的X、Y坐标,半径保持80-100的范围,和原逻辑的粒子初始距离一致 - 吸入动画的逻辑无需修改,粒子会从随机圆周位置向中心移动,实现全方向吸入的效果
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

