├── images
└── 2019-09-14 23.05.03.gif
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Package.swift
├── LICENSE
├── Sources
└── SiriWaveView
│ ├── Views
│ ├── SupportLine.swift
│ ├── SiriWaveView.swift
│ └── WaveView.swift
│ └── Models
│ └── SiriWave.swift
├── README.md
└── .gitignore
/images/2019-09-14 23.05.03.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/SiriWaveView/HEAD/images/2019-09-14 23.05.03.gif
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
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: "SiriWaveView",
8 | platforms: [.iOS(.v13), .tvOS(.v13), .macOS(.v10_15), .watchOS(.v6), .visionOS(.v1)],
9 | products: [
10 | .library(
11 | name: "SiriWaveView",
12 | targets: ["SiriWaveView"]),
13 | ],
14 | targets: [
15 | .target(
16 | name: "SiriWaveView"),
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Noah Chalifour
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 |
--------------------------------------------------------------------------------
/Sources/SiriWaveView/Views/SupportLine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SupportLine.swift
3 | // SwiftUI-SiriWaveView
4 | //
5 | // Created by Noah Chalifour on 2019-09-14.
6 | // Copyright © 2019 Noah Chalifour. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct SupportLine: View {
12 |
13 | var color: Color!
14 |
15 | var body: some View {
16 |
17 | GeometryReader { geometry in
18 |
19 | Path { path in
20 |
21 | let centerY = geometry.size.height / 2.0
22 |
23 | path.move(to: CGPoint(x: 0, y: centerY))
24 |
25 | path.addLines([
26 | CGPoint(x: 0, y: centerY),
27 | CGPoint(x: geometry.size.width, y: centerY)
28 | ])
29 |
30 | }
31 | .stroke(self.color, lineWidth: 2)
32 | .opacity(0.5)
33 |
34 | }
35 |
36 | }
37 |
38 | }
39 |
40 | struct SupportLine_Previews: PreviewProvider {
41 | static var previews: some View {
42 | SupportLine(color: Color(.white))
43 | .background(Color(.black))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/SiriWaveView/Views/SiriWaveView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SiriWaveView.swift
3 | // SwiftUI-SiriWaveView
4 | //
5 | // Created by Noah Chalifour on 2019-09-14.
6 | // Copyright © 2019 Noah Chalifour. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct SiriWaveView: View {
12 |
13 | let colors: [Color] = [
14 | Color(red: (173 / 255), green: (57 / 255), blue: (76 / 255)),
15 | Color(red: (48 / 255), green: (220 / 255), blue: (155 / 255)),
16 | Color(red: (25 / 255), green: (122 / 255), blue: (255 / 255))
17 | ]
18 | let supportLineColor: Color = .white
19 |
20 | @Binding var power: Double
21 |
22 | public init(power: Binding) {
23 | self._power = power
24 | }
25 |
26 | public var body: some View {
27 |
28 | GeometryReader { geometry in
29 |
30 | ZStack {
31 | SupportLine(color: self.supportLineColor)
32 | ForEach(0..
6 |
7 | 
8 |
9 | ## Overview
10 |
11 | This repo is a fork from [swiftui-siri-waveform-view](https://github.com/noahchalifour/swiftui-siri-waveform-view) repo which is now a public archive. This repo exposes the view as SPM Lib so it ca be easily integrated in modern SwiftUI projects.
12 |
13 | This is a very simple implementation of the Siri waveform in iOS9+ made with SwiftUI. The math behind the waveform is based on a great article which [can be found here](https://www.freecodecamp.org/news/how-i-built-siriwavejs-library-maths-and-code-behind-6971497ae5c1/) that explains the math and builds a Siri waveform in Javascript.
14 |
15 | ## Installation
16 |
17 | Swift Package Manager
18 |
19 | File > Swift Packages > Add Package Dependency Add - Add https://github.com/alfianlosari/SiriWaveView.git
20 |
21 | ## Usage
22 |
23 | ```swift
24 | import SiriWaveView
25 |
26 | struct MyView: View {
27 |
28 | @state var power: Double = 0.0
29 |
30 | var body: some View {
31 | VStack {
32 | SiriWaveView()
33 | .power(power)
34 | }
35 | }
36 | }
37 | ```
38 |
39 | Anytime the var power state is updated, the wave will animate automatically
40 |
41 | ## Original Author
42 |
43 | Noah Chalifour, chalifournoah@gmail.com
44 |
45 | ## License
46 |
47 | SwiftUI-SiriWaveView is available under the MIT license. See the LICENSE file for more info.
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
--------------------------------------------------------------------------------
/Sources/SiriWaveView/Views/WaveView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WaveView.swift
3 | // SwiftUI-SiriWaveView
4 | //
5 | // Created by Noah Chalifour on 2019-09-14.
6 | // Copyright © 2019 Noah Chalifour. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | private struct WaveGeometry {
12 |
13 | var wave: Wave
14 | var points: [CGPoint]
15 | var origin: CGPoint
16 |
17 | init(_ wave: Wave, in rect: CGRect) {
18 |
19 | self.wave = wave
20 | self.points = [CGPoint]()
21 | self.origin = CGPoint(x: 0, y: rect.midY)
22 |
23 | let xPoints = Array(stride(from: -rect.midX, to: rect.midX, by: 1.0))
24 |
25 | var coordinates = [[Double]](repeating: [0.0, 0.0], count: xPoints.count)
26 |
27 | for i in 0.. [Double] in
50 | return [coord[0], ((coord[1] - Double(rect.midY)) * -1) + Double(rect.midY)]
51 | })
52 |
53 | for coord in coordinates {
54 | self.points.append(CGPoint(x: coord[0], y: coord[1]))
55 | }
56 |
57 | }
58 |
59 | private func sine(x: Double, A: Double, k: Double, t: Double) -> Double {
60 |
61 | return A * sin((k * x) - t)
62 |
63 | }
64 |
65 | private func g(x: Double, t: Double, K: Double, k: Double) -> Double {
66 |
67 | return pow(K / (K + pow((k * x) - t, 2)), K)
68 |
69 | }
70 |
71 | private func attn(x: Double, A: Double, k: Double, t: Double) -> Double {
72 |
73 | return abs(sine(x: x, A: A, k: k, t: t) * g(x: x, t: t - (Double.pi / 2), K: 4, k: k))
74 |
75 | }
76 |
77 | }
78 |
79 | struct WaveShape: Shape {
80 |
81 | var wave: Wave
82 |
83 | func path(in rect: CGRect) -> Path {
84 |
85 | let geometry = WaveGeometry(wave, in: rect)
86 | var path = Path()
87 |
88 | path.move(to: geometry.origin)
89 | path.addLines(geometry.points)
90 |
91 | return path
92 |
93 | }
94 |
95 | var animatableData: Wave.AnimatableData {
96 |
97 | get {
98 | return wave.animatableData
99 | }
100 |
101 | set {
102 | wave.animatableData = newValue
103 | }
104 |
105 | }
106 |
107 | }
108 |
109 | struct WaveView: View {
110 |
111 | @Binding var power: Double
112 | var color: Color
113 |
114 | var body: some View {
115 |
116 | WaveShape(wave: .random(withPower: power))
117 | .fill(color)
118 |
119 | }
120 |
121 | }
122 |
123 | struct WaveView_Previews: PreviewProvider {
124 | static var previews: some View {
125 | WaveView(power: .constant(1), color: Color(.red))
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/SiriWaveView/Models/SiriWave.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SiriWave.swift
3 | // SwiftUI-SiriWaveView
4 | //
5 | // Created by Noah Chalifour on 2019-09-14.
6 | // Copyright © 2019 Noah Chalifour. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct Curve: Equatable {
12 |
13 | var power: Double
14 | var A: Double
15 | var k: Double
16 | var t: Double
17 |
18 | static func random(withPower power: Double) -> Curve {
19 |
20 | return Curve(
21 | power: power,
22 | A: Double.random(in: 0.1...1.0),
23 | k: Double.random(in: 0.6...0.9),
24 | t: Double.random(in: -1.0...4.0)
25 | )
26 |
27 | }
28 |
29 | }
30 |
31 | struct Wave: Equatable {
32 |
33 | var power: Double
34 | var curves: [Curve]
35 | var useCurves: Int
36 |
37 | static func random(withPower power: Double) -> Wave {
38 |
39 | let numCurves = Int.random(in: 2 ... 4)
40 |
41 | return Wave(
42 | power: power,
43 | curves: (0..<4).map { _ in
44 | return Curve.random(withPower: power)
45 | },
46 | useCurves: numCurves
47 | )
48 |
49 | }
50 |
51 | }
52 |
53 | //extension SiriWave.Curve: Animatable {
54 | //
55 | // typealias AnimatableData = AnimatablePair<
56 | // AnimatablePair, AnimatablePair>
57 | //
58 | // var animatableData: AnimatableData {
59 | //
60 | // get {
61 | // .init(.init(A, power), .init(k, t))
62 | // }
63 | //
64 | // set {
65 | // A = newValue.first.first
66 | // power = newValue.first.second
67 | // k = newValue.second.first
68 | // t = newValue.second.second
69 | // }
70 | //
71 | // }
72 | //
73 | //}
74 |
75 | // This part is temporary because you cannot create an
76 | // array of animatable data
77 |
78 | extension Wave: Animatable {
79 |
80 | typealias AnimatableData = AnimatablePair<
81 | AnimatablePair<
82 | AnimatablePair<
83 | AnimatablePair,
84 | AnimatablePair
85 | >,
86 | AnimatablePair<
87 | AnimatablePair,
88 | AnimatablePair
89 | >
90 | >,
91 | AnimatablePair<
92 | AnimatablePair<
93 | AnimatablePair,
94 | AnimatablePair
95 | >,
96 | AnimatablePair<
97 | AnimatablePair,
98 | AnimatablePair<
99 | AnimatablePair,
100 | AnimatablePair
101 | >
102 | >
103 | >
104 | >
105 |
106 | var animatableData: AnimatableData {
107 |
108 | get {
109 | .init(
110 | .init(
111 | .init(
112 | .init(curves[0].A, curves[0].power),
113 | .init(curves[0].k, curves[0].t)),
114 | .init(
115 | .init(curves[1].A, curves[1].power),
116 | .init(curves[1].k, curves[1].t))),
117 | .init(
118 | .init(
119 | .init(curves[2].A, curves[2].power),
120 | .init(curves[2].k, curves[2].t)),
121 | .init(
122 | .init(curves[3].A, curves[3].power),
123 | .init(
124 | .init(curves[3].k, curves[3].t),
125 | .init(power, .zero)))))
126 | }
127 |
128 | set {
129 | curves[0].A = newValue.first.first.first.first
130 | curves[0].power = newValue.first.first.first.second
131 | curves[0].k = newValue.first.first.second.first
132 | curves[0].t = newValue.first.first.second.second
133 |
134 | curves[1].A = newValue.first.second.first.first
135 | curves[1].power = newValue.first.second.first.second
136 | curves[1].k = newValue.first.second.second.first
137 | curves[1].t = newValue.first.second.second.second
138 |
139 | curves[2].A = newValue.second.first.first.first
140 | curves[2].power = newValue.second.first.first.second
141 | curves[2].k = newValue.second.first.second.first
142 | curves[2].t = newValue.second.first.second.second
143 |
144 | curves[3].A = newValue.second.second.first.first
145 | curves[3].power = newValue.second.second.first.second
146 | curves[3].k = newValue.second.second.second.first.first
147 | curves[3].t = newValue.second.second.second.first.second
148 |
149 | power = newValue.second.second.second.second.first
150 | }
151 |
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------