Compare commits

...

13 commits
1.0.3 ... main

Author SHA1 Message Date
Joe Fabisevich
1b4b1dda28
Fix ambiguous use of .pi (#83)
Some checks failed
Build / build (push) Has been cancelled
2026-02-20 10:10:51 -05:00
Itay Brenner
f650bd26c7
Set swift version (#80) 2025-02-18 13:52:15 -03:00
Itay Brenner
8381662164
Add badges to Readme.md (#76) 2025-02-18 13:47:40 -03:00
Joe Fabisevich
a504eb6d14
Adding custom WiggleRate, ShakeRate, and SpinRate options (#67) 2024-11-09 22:56:08 -05:00
Kabir Oberai
4f47e338c9
Fix SwiftPM build (#73) 2024-09-15 15:20:32 -04:00
Jakub Kiermasz
f2e23ad418
Start/stop engine when entering background/foreground (#70) 2024-08-12 14:45:25 -04:00
Adam McNight
f0d0f3e72d
Add tvOS support (#66) 2024-05-19 14:44:29 -04:00
Joe Fabisevich
5ac7140413
Adding custom phase length options for WiggleRate and ShakeRate (#62) 2024-05-19 14:33:56 -04:00
Nick Kohrn
ebf05a2239
[README] Fix Sound Effect Feedback Documentation (#64) 2024-04-06 12:13:48 -04:00
noahsmartin
392ad0d0b7
Add snapshots (#43) 2024-01-03 09:01:34 -08:00
noahsmartin
897afcde74
Add package (#50) 2023-12-26 17:31:16 -08:00
noahsmartin
76ced9f8e3
Update README.md (#56) 2023-12-26 14:56:08 -08:00
noahsmartin
fdc39e509c
Update gemfile (#54) 2023-12-25 20:48:44 -08:00
32 changed files with 401 additions and 120 deletions

View file

@ -8,12 +8,12 @@ on:
jobs:
build:
runs-on: macos-13
runs-on: macos-14
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Select Xcode version
run: sudo xcode-select -s '/Applications/Xcode_15.0.app/Contents/Developer'
run: sudo xcode-select -s '/Applications/Xcode_16.1.0.app/Contents/Developer'
- name: Run fastlane
env:
EMERGE_API_TOKEN: ${{ secrets.EMERGE_API_TOKEN }}

2
.gitignore vendored
View file

@ -1 +1,3 @@
*.DS_Store
.build
.swiftpm

View file

@ -103,6 +103,7 @@
54F15E5429D59D250054690B /* PulseExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F15E5329D59D250054690B /* PulseExample.swift */; };
54F15E5629D5A0D70054690B /* GlowExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F15E5529D5A0D70054690B /* GlowExample.swift */; };
B7A5DA8D2B0E853A0035FC0A /* Pow in Frameworks */ = {isa = PBXBuildFile; productRef = B7A5DA8C2B0E853A0035FC0A /* Pow */; };
FA13B8ED2B3BAF5600F58836 /* SnapshotPreferences in Frameworks */ = {isa = PBXBuildFile; productRef = FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -212,6 +213,7 @@
buildActionMask = 2147483647;
files = (
B7A5DA8D2B0E853A0035FC0A /* Pow in Frameworks */,
FA13B8ED2B3BAF5600F58836 /* SnapshotPreferences in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -420,6 +422,7 @@
name = "Pow Example";
packageProductDependencies = (
B7A5DA8C2B0E853A0035FC0A /* Pow */,
FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */,
);
productName = "Pow Example";
productReference = 546A2986292A31BB00A80DE2 /* Pow Example.app */;
@ -450,6 +453,7 @@
);
mainGroup = 546A297D292A31BB00A80DE2;
packageReferences = (
FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */,
);
productRefGroup = 546A2987292A31BB00A80DE2 /* Products */;
projectDirPath = "";
@ -717,7 +721,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example.Debug";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -749,7 +753,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@ -777,11 +781,27 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/EmergeTools/SnapshotPreviews-iOS";
requirement = {
kind = exactVersion;
version = 0.10.21;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
B7A5DA8C2B0E853A0035FC0A /* Pow */ = {
isa = XCSwiftPackageProductDependency;
productName = Pow;
};
FA13B8EC2B3BAF5600F58836 /* SnapshotPreferences */ = {
isa = XCSwiftPackageProductDependency;
package = FA13B8EB2B3BAF5600F58836 /* XCRemoteSwiftPackageReference "SnapshotPreviews-iOS" */;
productName = SnapshotPreferences;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 546A297E292A31BB00A80DE2 /* Project object */;

View file

@ -109,21 +109,21 @@ struct ExampleList: View {
}
}
struct PresentInfoAction {
var action: (any Example.Type) -> ()
struct PresentInfoAction: Sendable {
var action: @MainActor (any Example.Type) -> ()
init(action: @escaping (any Example.Type) -> Void) {
init(action: @escaping @MainActor (any Example.Type) -> Void) {
self.action = action
}
func callAsFunction<T: Example>(_ type: T.Type) {
@MainActor func callAsFunction<T: Example>(_ type: T.Type) {
action(type)
}
}
extension EnvironmentValues {
struct PresentInfoActionKey: EnvironmentKey {
static var defaultValue: PresentInfoAction? = nil
struct PresentInfoActionKey: EnvironmentKey {
static let defaultValue: PresentInfoAction? = nil
}
var presentInfoAction: PresentInfoAction? {

View file

@ -1,5 +1,6 @@
import Pow
import SwiftUI
import SnapshotPreferences
struct CheckoutExample: View, Example {
enum PaymentError: Error {
@ -271,5 +272,6 @@ struct CheckoutExample_Previews: PreviewProvider {
CheckoutExample()
.toolbar(.visible, for: .navigationBar)
}
.emergeSnapshotPrecision(0.99)
}
}

View file

@ -3,6 +3,10 @@ fastlane_require 'git'
default_platform(:ios)
def build_for_config(config)
package_file = "../Package.swift"
`sed -i '' 's/let enablePreviews = .*/let enablePreviews = #{config == "Debug" ? "true" : "false"}/' "#{package_file}"`
g = Git.open('.')
build_app(
project: "./Example/Pow Example.xcodeproj",

View file

@ -3,21 +3,21 @@ GEM
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.5)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.864.0)
aws-sdk-core (3.190.0)
aws-partitions (1.873.0)
aws-sdk-core (3.190.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.74.0)
aws-sdk-kms (1.75.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.141.0)
aws-sdk-s3 (1.142.0)
aws-sdk-core (~> 3, >= 3.189.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
@ -35,7 +35,7 @@ GEM
domain_name (0.6.20231109)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.105.0)
excon (0.108.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@ -64,7 +64,7 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastimage (2.3.0)
fastlane (2.217.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
@ -111,7 +111,7 @@ GEM
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
google-apis-androidpublisher_v3 (0.53.0)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.2)
addressable (~> 2.5, >= 2.5.1)
@ -131,7 +131,7 @@ GEM
google-cloud-core (1.6.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.0.1)
google-cloud-env (2.1.0)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.3.1)
google-cloud-storage (1.45.0)
@ -142,9 +142,9 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.9.0)
googleauth (1.9.1)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.0, >= 2.0.1)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
@ -191,7 +191,7 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)

View file

@ -3,12 +3,15 @@
import PackageDescription
let enablePreviews = false
let package = Package(
name: "Pow",
platforms: [
.iOS(.v15),
.macOS(.v12),
.macCatalyst(.v15)
.macCatalyst(.v15),
.tvOS(.v15)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
@ -19,15 +22,19 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/EmergeTools/SnapshotPreviews-iOS", exact: "0.10.21")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Pow",
dependencies: []),
dependencies: enablePreviews ? [.product(name: "SnapshotPreferences", package: "SnapshotPreviews-iOS", condition: .when(platforms: [.iOS]))] : [],
resources: [.process("Assets.xcassets")],
swiftSettings: enablePreviews ? [.define("EMG_PREVIEWS")] : nil),
.testTarget(
name: "PowTests",
dependencies: ["Pow"]),
]
],
swiftLanguageVersions: [.v5]
)

View file

@ -2,6 +2,10 @@
# Pow
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FPow%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/EmergeTools/Pow)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FPow%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/EmergeTools/Pow)
[![](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.emergetools.com%2Fapi%2Fv2%2Fpublic_new_build%3FexampleId%3Dpow.Pow%26platform%3Dios%26badgeOption%3Dversion_and_max_install_size%26buildType%3Drelease&query=$.badgeMetadata&label=Pow&logo=apple)](https://www.emergetools.com/app/example/ios/pow.Pow/release?utm_campaign=badge-data)
Delightful SwiftUI effects for your app.
[Check out other open source projects from Emerge Tools](https://www.emergetools.com/open-source)
@ -26,7 +30,7 @@ If you are moving from the previously closed source Pow framework to the new ope
Pow features a selection of [SwiftUI transitions](#transitions) as well as [Change Effects](#change-effects) that trigger every time a value is updated.
You can find previews of all effects [on the Pow website](https://movingparts.io/pow). If you have an iOS Developer Environment, you can check out the [Pow Example App](https://github.com/movingparts-io/Pow-Examples).
You can find previews of all effects [on the Pow website](https://movingparts.io/pow). If you have an iOS Developer Environment, you can check out the [Pow Example App](https://github.com/EmergeTools/Pow/tree/main/Example).
# Feedback & Contribution
@ -216,7 +220,7 @@ static func shine(angle: Angle, duration: Double = 1.0) -> AnyChangeEffect
Triggers a sound effect as feedback whenever a value changes.
This effect will not interrupt or duck any other audio that may currently playing. It may also not triggered based on the setting of the user's silent switch or playback device.
This effect will not interrupt or duck any other audio that may be currently playing. This effect is not guaranteed to be triggered; the effect running depends on the user's silent switch position and the current playback device.
To relay important information to the user, you should always accompany audio effects with visual cues.

View file

@ -12,11 +12,13 @@ public extension AnyChangeEffect {
enum ShakeRate {
case `default`
case fast
case phaseLength(CGFloat)
fileprivate var phaseLength: CGFloat {
switch self {
case .default: return 0.8
case .fast: return 0.3
case .phaseLength(let phaseLength): return phaseLength
}
}
}

View file

@ -1,5 +1,8 @@
import SwiftUI
import simd
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyConditionalEffect {
/// An effect that emits smoke from the view.
@ -54,7 +57,7 @@ private struct SmokeEffect: ViewModifier, Continuous {
GeometryReader { proxy in
ZStack {
ForEach(Array(particles.enumerated()), id: \.element) { (offset, particle) in
#if os(iOS) || os(visionOS)
#if os(iOS) || os(visionOS) || os(tvOS)
let image = UIImage(named: particle, in: .module, with: nil)!.cgImage!
#elseif os(macOS)
let image = Bundle.module.image(forResource: particle)!.cgImage(forProposedRect: nil, context: nil, hints: nil)!
@ -67,7 +70,7 @@ private struct SmokeEffect: ViewModifier, Continuous {
}
}
#if os(iOS) || os(visionOS)
#if os(iOS) || os(visionOS) || os(tvOS)
private class EmitterView: UIView {
override class var layerClass : AnyClass {
return CAEmitterLayer.self
@ -169,7 +172,7 @@ private struct SmokeLayerView: ViewRepresentable {
}
}
#if DEBUG
#if DEBUG && !os(tvOS)
struct ContinuousParticleEffect_Previews: PreviewProvider {
private struct Preview: View {
@State
@ -336,30 +339,35 @@ struct ContinuousParticleEffect_Previews: PreviewProvider {
}
static var previews: some View {
Group {
Preview()
.preferredColorScheme(.dark)
.previewDisplayName("Dark")
.preferredColorScheme(.dark)
.previewDisplayName("Dark")
Preview()
.preferredColorScheme(.light)
.previewDisplayName("Light")
.preferredColorScheme(.light)
.previewDisplayName("Light")
PreviewS()
.preferredColorScheme(.dark)
.previewDisplayName("Small")
.preferredColorScheme(.dark)
.previewDisplayName("Small")
Preview2()
.preferredColorScheme(.dark)
.previewDisplayName("Large")
.preferredColorScheme(.dark)
.previewDisplayName("Large")
Preview3()
.preferredColorScheme(.dark)
.previewDisplayName("Particle Layer")
.preferredColorScheme(.dark)
.previewDisplayName("Particle Layer")
#if os(iOS)
#if os(iOS)
PreviewLayer()
.previewDisplayName("Emitter Layer")
#endif
.previewDisplayName("Emitter Layer")
#endif
PreviewAlt()
.preferredColorScheme(.dark)
.previewDisplayName("Emitter Dark")
.preferredColorScheme(.dark)
.previewDisplayName("Emitter Dark")
}
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif

View file

@ -5,18 +5,21 @@ public extension AnyChangeEffect {
enum SpinRate {
case `default`
case fast
fileprivate var maximumVelocity: Angle {
switch self {
case .fast: return .degrees(360 * 4)
case .default: return .degrees(360 * 2)
}
}
case velocity(initial: Angle, maximum: Angle, additional: Angle)
fileprivate var initialVelocity: Angle {
switch self {
case .fast: return .degrees(900)
case .default: return .degrees(360)
case .velocity(let initial, _, _): return initial
}
}
fileprivate var maximumVelocity: Angle {
switch self {
case .fast: return .degrees(360 * 4)
case .default: return .degrees(360 * 2)
case .velocity(_, let maximum, _): return maximum
}
}
@ -24,6 +27,7 @@ public extension AnyChangeEffect {
switch self {
case .fast: return .degrees(900)
case .default: return .degrees(360)
case .velocity(_, _, let additional): return additional
}
}
}
@ -40,10 +44,11 @@ public extension AnyChangeEffect {
/// - anchor: The location with a default of center that defines a point in 3D space about which the rotation is anchored.
/// - anchorZ: The location with a default of 0 that defines a point in 3D space about which the rotation is anchored.
/// - perspective: The relative vanishing point with a default of 1 / 6 for this rotation.
/// - perspective: An additional multipler you can provide to speed up the animation's runtime.
/// - rate: The rate of the spin.
static func spin(axis: (x: CGFloat, y: CGFloat, z: CGFloat), anchor: UnitPoint = .center, anchorZ: CGFloat = 0, perspective: CGFloat = 1 / 6, rate: SpinRate = .default) -> AnyChangeEffect {
static func spin(axis: (x: CGFloat, y: CGFloat, z: CGFloat), anchor: UnitPoint = .center, anchorZ: CGFloat = 0, perspective: CGFloat = 1 / 6, multiplier speedBoost: CGFloat = 0.0, rate: SpinRate = .default) -> AnyChangeEffect {
.simulation { change in
SpinSimulationModifier(impulseCount: change, axis: axis, anchor: anchor, anchorZ: anchorZ, perspective: perspective, rate: rate)
SpinSimulationModifier(impulseCount: change, axis: axis, anchor: anchor, anchorZ: anchorZ, perspective: perspective, additionalSpeed: speedBoost, rate: rate)
}
}
}
@ -63,6 +68,8 @@ internal struct SpinSimulationModifier: ViewModifier, Simulative {
var perspective: CGFloat
var additionalSpeed: CGFloat
var rate: AnyChangeEffect.SpinRate
@State
@ -120,7 +127,7 @@ internal struct SpinSimulationModifier: ViewModifier, Simulative {
if abs(angleVelocity.degrees) > 240 {
newValue = angle.degrees + angleVelocity.degrees * step
newVelocity = angleVelocity.degrees * 0.99
newVelocity = angleVelocity.degrees * (0.99 - self.additionalSpeed)
targetAngle = .degrees((angle.degrees / 360.0).rounded(.up) * 360.0)
} else if spring.response > 0 {
(newValue, newVelocity) = spring.value(

View file

@ -10,11 +10,13 @@ public extension AnyChangeEffect {
enum WiggleRate {
case `default`
case fast
case phaseLength(CGFloat)
fileprivate var phaseLength: CGFloat {
switch self {
case .default: return 0.8
case .fast: return 0.3
case .phaseLength(let phaseLength): return phaseLength
}
}
}

View file

@ -2,6 +2,7 @@ import Foundation
@available(iOS 16.0, *)
@available(macOS 13.0, *)
@available(tvOS 16.0, *)
internal extension Duration {
var timeInterval: TimeInterval {
TimeInterval(components.seconds) + TimeInterval(components.attoseconds) / 1e18

View file

@ -1,3 +1,4 @@
#if !os(tvOS)
import SwiftUI
struct AngleControl<Label: View>: View {
@ -132,3 +133,4 @@ struct AngleControl_Previews: PreviewProvider {
.padding()
}
}
#endif

View file

@ -5,8 +5,21 @@ import CoreHaptics
internal struct Haptics {
private static var engine: CHHapticEngine? = {
return try? CHHapticEngine()
let engine = try? CHHapticEngine()
addHapticEngineObservers()
return engine
}()
private static func addHapticEngineObservers() {
// Without stopping the CHHapticEngine when entering background mode, haptics are not played when the app enters the foreground.
// See https://github.com/EmergeTools/Pow/issues/69
NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in
engine?.stop()
}
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in
try? engine?.start()
}
}
private static var supportsHaptics = CHHapticEngine.capabilitiesForHardware().supportsHaptics

View file

@ -0,0 +1,62 @@
//
// ProgressableAnimation.swift
//
//
// Created by Noah Martin on 11/30/23.
//
import Foundation
import SwiftUI
#if DEBUG
typealias DebugProgressableAnimation = ProgressableAnimation
#else
typealias DebugProgressableAnimation = Animatable
#endif
protocol ProgressableAnimation: Animatable {
var progress: CGFloat { get set }
}
extension ProgressableAnimation where AnimatableData == CGFloat {
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
}
#if DEBUG
protocol PreviewableAnimation {
associatedtype Animation: ProgressableAnimation & ViewModifier
static var animation: Animation { get }
static var content: any View { get }
}
extension PreviewableAnimation {
static var content: any View {
RoundedRectangle(
cornerRadius: 8,
style: .continuous)
.fill(Color.blue)
.frame(width: 80, height: 80)
}
}
extension PreviewableAnimation {
static var previews: AnyView {
let c = self.content
let anyContent = AnyView(c)
let modifiers = [0, 0.25, 0.5, 0.75, 1].map { i in
var copy = self.animation
copy.progress = i
return copy
}
return AnyView(ForEach(Array(modifiers.enumerated()), id: \.offset) { i, modifier in
anyContent.modifier(modifier)
.previewDisplayName("\(String(describing: Animation.self))-\(i)")
})
}
}
#endif

View file

@ -26,3 +26,5 @@ internal struct Scaled<V: ViewModifier & Animatable>: ViewModifier, Animatable {
content.modifier(base.animation(nil))
}
}
extension Scaled: ProgressableAnimation where V.AnimatableData == CGFloat { }

View file

@ -54,6 +54,7 @@ public struct AnyConditionalEffect {
/// - interval: The duration between each change effect.
@available(iOS 16.0, *)
@available(macOS 13.0, *)
@available(tvOS 16.0, *)
public static func `repeat`(_ effect: AnyChangeEffect, every interval: Duration) -> AnyConditionalEffect {
AnyConditionalEffect(guts: .repeating(effect, interval.timeInterval))
}

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// A transition that drops the view down from the top.
@ -16,7 +19,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
internal struct Anvil: ViewModifier, ProgressableAnimation, AnimatableModifier {
var animatableData: CGFloat = 0
#if os(iOS)
@ -28,11 +31,6 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
self.animatableData = animatableData
}
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
/// Fraction of the animation spent on the view falling down.
let fall: CGFloat = 0.1
@ -111,7 +109,7 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
let offsetX = maxOffsetX * (relativeX - 0.5) * 2 * .random(in: 0.8 ... 1.2, using: &rng)
let offsetY = CGFloat.random(in: -maxOffsetY / 2 ... maxOffsetY / 2, using: &rng) + (t * t) * -50
var scale = 1 + 0.6 * (1 - pow(sin(relativeX * .pi), 0.4)) + .random(in: 0 ... 0.2, using: &rng)
var scale = 1 + 0.6 * (1 - pow(sin(relativeX * CGFloat.pi), 0.4)) + .random(in: 0 ... 0.2, using: &rng)
scale *= 0.8 + (dustT * 0.2)
scale /= 3
scale *= 1 - pow(2, -50 * dustT)
@ -190,7 +188,7 @@ internal struct Anvil: ViewModifier, Animatable, AnimatableModifier {
ctx.translateBy(x: center.x, y: center.y)
ctx.scaleBy(x: scale, y: scale)
ctx.opacity = Double(pow(sin(speckT * .pi), 0.2))
ctx.opacity = Double(pow(sin(speckT * CGFloat.pi), 0.2))
ctx.fill(speck, with: .color(Color(white: .random(in: 0.75 ... 0.9, using: &rng))))
}
}
@ -209,6 +207,21 @@ extension EdgeInsets {
}
#if os(iOS) && DEBUG
struct Anvil_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Anvil {
Anvil(animatableData: 0)
}
static var content: any View {
RoundedRectangle(
cornerRadius: 8,
style: .continuous)
.fill(Color.blue)
.frame(width: 80, height: 80)
.preferredColorScheme(.dark)
}
}
@available(iOS 15.0, *)
struct Anvil_Previews: PreviewProvider {
struct Item: Identifiable {
@ -280,6 +293,9 @@ struct Anvil_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif

View file

@ -30,7 +30,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Blinds: ViewModifier, Animatable, AnimatableModifier, Hashable {
internal struct Blinds: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
var slatWidth: CGFloat
var style: AnyTransition.MovingParts.BlindsStyle
@ -39,11 +39,6 @@ internal struct Blinds: ViewModifier, Animatable, AnimatableModifier, Hashable {
var animatableData: CGFloat
private var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
content
.mask {
@ -108,6 +103,13 @@ private struct BlindsShape: Shape {
}
#if os(iOS) && DEBUG
struct Blinds_Preview: PreviewableAnimation & PreviewProvider {
static var animation: Blinds {
Blinds(slatWidth: 20, style: .venetian, isStaggered: false, animatableData: 0)
}
}
@available(iOS 15.0, *)
struct Blinds_Previews: PreviewProvider {
struct Preview: View {

View file

@ -22,7 +22,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Blur: ViewModifier, Animatable, AnimatableModifier, Hashable {
internal struct Blur: ViewModifier, DebugProgressableAnimation, AnimatableModifier, Hashable {
var animatableData: CGFloat {
get { radius }
set { radius = newValue }
@ -37,6 +37,12 @@ internal struct Blur: ViewModifier, Animatable, AnimatableModifier, Hashable {
}
#if os(iOS) && DEBUG
struct Blur_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Blur {
Blur(radius: 30)
}
}
@available(iOS 15.0, *)
struct Blur_Previews: PreviewProvider {
struct Preview: View {

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// A transition that moves the view down with any overshoot resulting in an
@ -18,7 +21,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Boing: Animatable, GeometryEffect {
internal struct Boing: DebugProgressableAnimation, GeometryEffect {
var edge: Edge
var animatableData: CGFloat = 0
@ -104,6 +107,12 @@ internal struct Boing: Animatable, GeometryEffect {
}
#if os(iOS) && DEBUG
struct Boing_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Scaled<Boing> {
Scaled(Boing(.top, animatableData: 0))
}
}
@available(iOS 15.0, *)
struct Bounce_Previews: PreviewProvider {
struct Item: Identifiable {
@ -218,6 +227,9 @@ struct Bounce_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
@ -290,6 +302,9 @@ struct Boing_2_Previews: PreviewProvider {
NavigationView {
Preview()
}
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// A transition using a clockwise sweep around the centerpoint of the view.
@ -18,7 +21,8 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
internal struct Clock: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
var origin: UnitPoint
var animatableData: AnimatablePair<CGFloat, CGFloat>
@ -29,7 +33,8 @@ internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
}
var progress: CGFloat {
animatableData.first
get { animatableData.first }
set { animatableData.first = newValue }
}
var blurRadius: CGFloat {
@ -81,6 +86,12 @@ internal struct Clock: ViewModifier, Animatable, AnimatableModifier {
}
#if os(iOS) && DEBUG
struct Clock_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Clock {
Clock(origin: .center, blurRadius: 0, progress: 0)
}
}
struct Clock_Previews: PreviewProvider {
struct Item: Identifiable {
var color: Color
@ -225,6 +236,9 @@ struct Clock_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif

View file

@ -20,14 +20,9 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Snapshot: ViewModifier, Animatable, AnimatableModifier, Hashable {
internal struct Snapshot: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
var animatableData: CGFloat = 0
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
content
.saturation(0.5 + 0.5 * clamp(progress))
@ -38,14 +33,9 @@ internal struct Snapshot: ViewModifier, Animatable, AnimatableModifier, Hashable
}
}
internal struct ExposureFade: ViewModifier, Animatable, AnimatableModifier, Hashable {
internal struct ExposureFade: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
var animatableData: CGFloat = 0
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
content
.opacity(Double(1.0 - pow(2.0, -10.0 * progress)))
@ -55,6 +45,18 @@ internal struct ExposureFade: ViewModifier, Animatable, AnimatableModifier, Hash
}
#if os(iOS) && DEBUG
struct Snapshot_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Snapshot {
Snapshot(animatableData: 0)
}
}
struct FilmExposure_Preview: PreviewableAnimation, PreviewProvider {
static var animation: ExposureFade {
ExposureFade(animatableData: 0)
}
}
@available(iOS 15.0, *)
struct ExoposureFade_Previews: PreviewProvider {
struct Preview: View {

View file

@ -21,16 +21,11 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Flicker: ViewModifier, Animatable, AnimatableModifier, Hashable {
internal struct Flicker: ViewModifier, ProgressableAnimation, AnimatableModifier, Hashable {
var count: Int
var animatableData: CGFloat
private var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
private var isVisible: Bool {
(progress * CGFloat(count)).remainder(dividingBy: 1) >= 0
}
@ -43,6 +38,12 @@ internal struct Flicker: ViewModifier, Animatable, AnimatableModifier, Hashable
}
#if os(iOS) && DEBUG
struct Flicker_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Flicker {
Flicker(count: 1, animatableData: 0)
}
}
@available(iOS 15.0, *)
struct Flicker_Previews: PreviewProvider {
struct Preview: View {

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// A transitions that shows the view by combining a diagonal wipe with a
@ -44,7 +47,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Glare: ViewModifier, Animatable, AnimatableModifier {
internal struct Glare: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
var animatableData: CGFloat = 0
var angle: Angle
@ -129,8 +132,37 @@ internal struct Glare: ViewModifier, Animatable, AnimatableModifier {
}
#if os(iOS) && DEBUG
@available(iOS 16.0, *)
struct Glare_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Glare {
Glare(.degrees(45), color: .white, increasedBrightness: true, animatableData: 0)
}
static var content: any View {
Glare_Previews.makeRect(start: .indigo, end: .purple)
.frame(width: 100, height: 100)
.preferredColorScheme(.dark)
}
}
@available(iOS 16.0, *)
struct Glare_Previews: PreviewProvider {
static func makeRect(start: Color, end: Color) -> some View {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(LinearGradient(
colors: [start, end],
startPoint: .topLeading,
endPoint: .bottom
))
.compositingGroup()
.overlay {
Text("Hello\nWorld")
.foregroundStyle(.white.shadow(.inner(radius: 0.5)))
}
.font(.system(.largeTitle, design: .rounded).weight(.medium))
.multilineTextAlignment(.center)
}
struct Item: Identifiable {
var color1: Color
var color2: Color
@ -211,19 +243,7 @@ struct Glare_Previews: PreviewProvider {
ForEach(items.indices, id: \.self) { index in
let item = items[index]
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(LinearGradient(
colors: [item.color1, item.color2],
startPoint: .topLeading,
endPoint: .bottom
))
.compositingGroup()
.overlay {
Text("Hello\nWorld")
.foregroundStyle(.white.shadow(.inner(radius: 0.5)))
}
.font(.system(.largeTitle, design: .rounded).weight(.medium))
.multilineTextAlignment(.center)
Glare_Previews.makeRect(start: item.color1, end: item.color2)
.transition(
.asymmetric(
insertion: .movingParts.glare(angle: angle),
@ -254,6 +274,9 @@ struct Glare_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif

View file

@ -15,7 +15,7 @@ public extension AnyTransition.MovingParts {
}
}
private struct Iris: ViewModifier, Animatable, AnimatableModifier {
struct Iris: ViewModifier, DebugProgressableAnimation, AnimatableModifier {
var origin: UnitPoint
var blurRadius: CGFloat
@ -58,6 +58,12 @@ private struct Iris: ViewModifier, Animatable, AnimatableModifier {
}
#if os(iOS) && DEBUG
struct Iris_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Iris {
Iris(origin: .center, animatableData: 0)
}
}
@available(iOS 15.0, *)
struct Mask_Previews: PreviewProvider {
struct Preview: View {

View file

@ -16,18 +16,13 @@ public extension AnyTransition.MovingParts {
}
}
private struct Poof: ViewModifier, Animatable, AnimatableModifier {
struct Poof: ViewModifier, ProgressableAnimation, AnimatableModifier {
var animatableData: CGFloat = 0
internal init(animatableData: CGFloat) {
self.animatableData = animatableData
}
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
let frame = (6 * progress).rounded()
@ -56,6 +51,25 @@ private struct Poof: ViewModifier, Animatable, AnimatableModifier {
}
#if os(iOS) && DEBUG
struct Proof_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Poof {
Poof(animatableData: 0)
}
static var content: any View {
ZStack {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(Color.accentColor)
Text("Hello\nWorld!")
.foregroundColor(.white)
.multilineTextAlignment(.center)
.font(.system(.title, design: .rounded))
}
.frame(width: 300, height: 150)
}
}
@available(iOS 15.0, *)
struct Poof_Previews: PreviewProvider {
struct Preview: View {

View file

@ -51,7 +51,7 @@ public extension AnyTransition.MovingParts {
}
@available(iOS 15.0, *)
private struct Pop: AnimatableModifier, Animatable, ViewModifier {
struct Pop: AnimatableModifier, ProgressableAnimation, ViewModifier {
var animatableData: CGFloat = 0
var style: AnyShapeStyle
@ -63,11 +63,6 @@ private struct Pop: AnimatableModifier, Animatable, ViewModifier {
self.style = style
}
var progress: CGFloat {
get { animatableData }
set { animatableData = newValue }
}
func body(content: Content) -> some View {
let t = clamp(2 * (progress - 1/2.5))
@ -170,6 +165,19 @@ private struct Pop: AnimatableModifier, Animatable, ViewModifier {
}
#if os(iOS) && DEBUG
struct Pop_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Pop {
Pop(style: AnyShapeStyle(.tint), animatableData: 0)
}
static var content: any View {
Image(systemName: "heart.fill")
.foregroundColor(.red)
.tint(.red)
.preferredColorScheme(.dark)
}
}
@available(iOS 15.0, *)
struct Pop_Previews: PreviewProvider {
struct Preview: View {

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// The direction from which to animate in during a `skid` transition's insertion.
@ -26,7 +29,7 @@ public extension AnyTransition.MovingParts {
}
}
internal struct Skid: Animatable, GeometryEffect {
internal struct Skid: DebugProgressableAnimation, GeometryEffect {
var direction: AnyTransition.MovingParts.SkidDirection
var animatableData: CGFloat = 0
@ -70,6 +73,29 @@ internal struct Skid: Animatable, GeometryEffect {
}
#if os(iOS) && DEBUG
struct Skid_Preview: PreviewableAnimation, PreviewProvider {
static var animation: Skid {
Skid(.leading)
}
static var content: some View {
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(Color.orange)
.overlay {
Text("Jell-O\nWorld")
.blendMode(.difference)
.offset(x: 2, y: 2)
}
.compositingGroup()
.overlay {
Text("Jell-O\nWorld")
}
.font(.system(.headline, design: .rounded).weight(.black))
.multilineTextAlignment(.center)
.frame(width: 150, height: 150)
}
}
@available(iOS 15.0, *)
struct Skid_Previews: PreviewProvider {
struct Item: Identifiable {
@ -182,6 +208,9 @@ struct Skid_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}

View file

@ -1,4 +1,7 @@
import SwiftUI
#if os(iOS) && EMG_PREVIEWS
import SnapshotPreferences
#endif
public extension AnyTransition.MovingParts {
/// A transition that dissolves the view into many small particles.
@ -236,6 +239,9 @@ struct Vanish_Previews: PreviewProvider {
.navigationBarHidden(true)
}
.environment(\.colorScheme, .dark)
#if os(iOS) && EMG_PREVIEWS
.emergeSnapshotPrecision(0)
#endif
}
}
#endif