├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
└── Sources
└── DelayedTransitionView
├── DelayedPresentationConfiguration.swift
├── DelayedPresentationViewModifier.swift
└── DelayedTransitionView.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
92 | # SPM
93 | .swiftpm/
94 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 David Steinacher
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.8
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DelayedTransitionView",
8 | platforms: [
9 | .iOS(.v14),
10 | .tvOS(.v14)
11 | ],
12 | products: [
13 | .library(
14 | name: "DelayedTransitionView",
15 | targets: ["DelayedTransitionView"]
16 | ),
17 | ],
18 | targets: [
19 | .target(name: "DelayedTransitionView")
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
DelayedTransitionView
4 | Appear animation transition View for SwiftUI
5 |
6 |
7 |
8 | - [Installation](#installation)
9 | - [Swift Package Manager](#using-swift-package-manager)
10 | - [Usage](#usage)
11 | - [Example](#example)
12 |
13 | ## Installation
14 | ### Using [Swift Package Manager](https://swift.org/package-manager/)
15 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift frameworks. It integrates with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
16 |
17 | #### Using Xcode
18 | To integrate using Xcode 13, open your Project file and specify it in `Project > Package Dependencies` using the following URL:
19 |
20 | ```
21 | https://github.com/stonko1994/DelayedTransitionView.git
22 | ```
23 |
24 | ## Usage
25 | ```swift
26 | import DelayedTransitionView
27 | ```
28 |
29 | Add a `DelayedTransitionView` to your view. This view acts as container view for all views that should be animated.
30 |
31 | ```swift
32 | DelayedTransitionView {
33 | ...
34 | }
35 | ```
36 |
37 | _The `DelayedTransitionView` takes additional configuration properties. (See code documentation for details)_
38 |
39 | Use the `.delayedPresentation(viewIndex: x)` view modifier to specify which views should be animated.
40 |
41 | ```swift
42 | DelayedTransitionView {
43 | VStack {
44 | Spacer()
45 | Text("First view")
46 | .delayedPresentation(viewIndex: 0)
47 | Spacer()
48 | Text("Second view")
49 | .delayedPresentation(viewIndex: 1)
50 | Spacer()
51 | Text("Third view")
52 | .delayedPresentation(viewIndex: 2)
53 | Spacer()
54 | }
55 | }
56 | ```
57 |
58 | In this example only the `Text` views will appear be animated.
59 |
60 | For more control over the individual animations, checkout the additional configuration properties. (See code documentation for details)
61 | _It is possible to use the same `viewIndex` for multiple views if they should appear at the same time. Also it does not need to start with `0`_
62 |
63 | ## Example
64 |
65 |
66 |
67 |
68 |
69 | ---
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Sources/DelayedTransitionView/DelayedPresentationConfiguration.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | class DelayedPresentationConfiguration: ObservableObject {
4 | enum DefaultValues {
5 | static let animationDuration: Double = 0.6
6 | static let delay: Double = 0.2
7 | static let offset: Double = 20
8 | static let orientation: Axis.Set = .vertical
9 | }
10 |
11 | /// Acts as the trigger for the animation
12 | @Published var isVisible = false
13 |
14 | /// Define the duration of the animation in seconds
15 | let animationDuration: Double
16 | /// Define the delay between items in seconds
17 | let delay: Double
18 | /// Define the movement when the view appears
19 | let offset: Double
20 | /// Define the orientation when the view appears
21 | let orientation: Axis.Set
22 |
23 | init(
24 | animationDuration: Double,
25 | delay: Double,
26 | offset: Double,
27 | orientation: Axis.Set
28 | ) {
29 | self.animationDuration = animationDuration
30 | self.delay = delay
31 | self.offset = offset
32 | self.orientation = orientation
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/DelayedTransitionView/DelayedPresentationViewModifier.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct DelayedPresentationViewModifier: ViewModifier {
4 | @EnvironmentObject var delayedPresentationState: DelayedPresentationConfiguration
5 |
6 | let viewNumber: Int
7 | let showAnimationDuration: Double?
8 | let itemDelay: Double?
9 | let offset: Double?
10 | let orientation: Axis.Set?
11 |
12 | func body(content: Content) -> some View {
13 | content
14 | .opacity(delayedPresentationState.isVisible ? 1 : 0)
15 | .modifier(ConditionalOffsetViewModifier(offset: offset, orientation: orientation))
16 | .animation(
17 | .easeOut(duration: showAnimationDuration ?? delayedPresentationState.animationDuration)
18 | .delay(0.1 + (Double(viewNumber) * (itemDelay ?? delayedPresentationState.delay))),
19 | value: delayedPresentationState.isVisible
20 | )
21 | }
22 | }
23 |
24 | private struct ConditionalOffsetViewModifier: ViewModifier {
25 | @EnvironmentObject var delayedPresentationState: DelayedPresentationConfiguration
26 |
27 | let offset: Double?
28 | let orientation: Axis.Set?
29 |
30 | func body(content: Content) -> some View {
31 | if (orientation ?? delayedPresentationState.orientation) == .vertical {
32 | content
33 | .offset(y: delayedPresentationState.isVisible ? 0 : (offset ?? delayedPresentationState.offset))
34 | } else {
35 | content
36 | .offset(x: delayedPresentationState.isVisible ? 0 : (offset ?? delayedPresentationState.offset))
37 | }
38 | }
39 | }
40 |
41 | public extension View {
42 | /// Enables the view to be transitioned. Must be used within a `DelayedTransitionView`
43 | /// - Parameters:
44 | /// - viewIndex: The index of the view. Starts at `0`.
45 | /// - animationDuration: The duration of the animation in seconds. Default `0.6`. Overrides the value of `DelayedTransitionView`.
46 | /// - delay: The delay before the animation starts. Default `0.2`. Overrides the value of `DelayedTransitionView`.
47 | /// - offset: The `y` offset for the view which will be animated. Default is `20`. Overrides the value of `DelayedTransitionView`.
48 | /// - orientation: The orientation in which the view should appear. Default is `.vertical`. Overrides the value of `DelayedTransitionView`.
49 | func delayedPresentation(
50 | viewIndex: Int,
51 | animationDuration: Double? = nil,
52 | delay: Double? = nil,
53 | offset: Double? = nil,
54 | orientation: Axis.Set? = nil
55 | ) -> some View {
56 | modifier(
57 | DelayedPresentationViewModifier(
58 | viewNumber: viewIndex,
59 | showAnimationDuration: animationDuration,
60 | itemDelay: delay,
61 | offset: offset,
62 | orientation: orientation
63 | )
64 | )
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/DelayedTransitionView/DelayedTransitionView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | /// Provides delayed visibility transitions.
4 | public struct DelayedTransitionView: View {
5 | @StateObject private var delayedPresentationConfiguration: DelayedPresentationConfiguration
6 |
7 | private let content: () -> Content
8 |
9 | /// Provides delayed visibility transitions.
10 | /// - Parameters:
11 | /// - animationDuration: The duration of the animation in seconds. Default `0.6`
12 | /// - delay: The delay before the animation starts. Default `0.2`
13 | /// - offset: The `y` offset for the view which will be animated. Default is `20`
14 | /// - orientation: The orientation in which the view should appear. Default is `.vertical`
15 | /// - content: A view builder that creates the content of this stack.
16 | public init(
17 | animationDuration: Double? = nil,
18 | delay: Double? = nil,
19 | offset: Double? = nil,
20 | orientation: Axis.Set? = nil,
21 | @ViewBuilder content: @escaping () -> Content
22 | ) {
23 | self.content = content
24 |
25 | let configuration = DelayedPresentationConfiguration(
26 | animationDuration: animationDuration ?? DelayedPresentationConfiguration.DefaultValues.animationDuration,
27 | delay: delay ?? DelayedPresentationConfiguration.DefaultValues.delay,
28 | offset: offset ?? DelayedPresentationConfiguration.DefaultValues.offset,
29 | orientation: orientation ?? DelayedPresentationConfiguration.DefaultValues.orientation
30 | )
31 | self._delayedPresentationConfiguration = StateObject(wrappedValue: configuration)
32 | }
33 |
34 | public var body: some View {
35 | content()
36 | .environmentObject(delayedPresentationConfiguration)
37 | .onAppear {
38 | delayedPresentationConfiguration.isVisible = true
39 | }
40 | }
41 | }
42 |
43 | struct DelayedTransitionView_Preview: PreviewProvider {
44 | static var previews: some View {
45 | DelayedTransitionView {
46 | VStack {
47 | Spacer()
48 | Text("First view")
49 | .delayedPresentation(viewIndex: 0)
50 | Spacer()
51 | Text("Second view")
52 | .delayedPresentation(viewIndex: 1)
53 | Spacer()
54 | Text("Third view")
55 | .delayedPresentation(viewIndex: 2)
56 | Spacer()
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------