├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── UnifiedBlurHash.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── UnifiedBlurHash
│ ├── Math.swift
│ ├── Resources
│ └── sunflower.jpg
│ ├── SwiftUI.Image+BlurHash.swift
│ ├── UnifiedBlurHash.swift
│ ├── UnifiedImage+Decode.swift
│ ├── UnifiedImage+Encode.swift
│ ├── UnifiedImage+Scale.swift
│ └── UnifiedImage.swift
├── Tests
└── UnifiedBlurHashTests
│ ├── UnifiedBlurHashTests.swift
│ ├── UnifiedImageResizingTests.swift
│ └── Utilities.swift
└── demo
├── demo.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── demo.xcscheme
│ └── watchOS-demo Watch App.xcscheme
├── demo
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── sunflower.imageset
│ │ ├── Contents.json
│ │ └── sunflower.jpg
├── ContentView.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── demo.entitlements
└── demoApp.swift
├── demoTests
└── demoTests.swift
├── demoUITests
├── demoUITests.swift
└── demoUITestsLaunchTests.swift
├── watchOS-demo Watch App
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── sunflower.imageset
│ │ ├── Contents.json
│ │ └── pexels-lil-artsy-1624076-2.jpg
├── ContentView.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
└── watchOS_demoApp.swift
├── watchOS-demo Watch AppTests
└── watchOS_demo_Watch_AppTests.swift
└── watchOS-demo Watch AppUITests
├── watchOS_demo_Watch_AppUITests.swift
└── watchOS_demo_Watch_AppUITestsLaunchTests.swift
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Swift project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
3 |
4 | name: Swift Build and Test
5 |
6 | on:
7 | workflow_dispatch:
8 | push:
9 | branches: [main]
10 | pull_request:
11 | branches: [main]
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.ref }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | build:
19 | name: Build and Test
20 | runs-on: macos-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: List available Xcode versions
24 | run: ls /Applications | grep Xcode
25 | - name: Set up Xcode version
26 | run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
27 | - name: Show current version of Xcode
28 | run: xcodebuild -version
29 | - name: Show current version of Swift
30 | run: swift -version
31 | - name: Build
32 | run: swift build -v
33 | - name: Run tests
34 | run: swift test -v
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/UnifiedBlurHash.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 iankoex
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.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: "UnifiedBlurHash",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "UnifiedBlurHash",
12 | targets: ["UnifiedBlurHash"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "UnifiedBlurHash",
23 | dependencies: [],
24 | resources: [
25 | .process("Resources")
26 | ]
27 | ),
28 | .testTarget(
29 | name: "UnifiedBlurHashTests",
30 | dependencies: ["UnifiedBlurHash"]
31 | ),
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UnifiedBlurHash
2 |
3 | [](https://swiftpackageindex.com/iankoex/UnifiedBlurHash)
4 | [](https://swiftpackageindex.com/iankoex/UnifiedBlurHash)
5 | [](https://github.com/iankoex/UnifiedBlurHash/actions/workflows/swift.yml)
6 |
7 | Extensions of `UIImage` and `NSImage` to encode and decode blur hashes, based on [Wolt's Implementation](https://github.com/woltapp/blurhash) for Swift (iOS).
8 |
9 | Based on [Mortenjust's Implementation](https://github.com/mortenjust/Blurhash-macos.git) for macOS
10 |
11 | Screenshot
12 |
13 | 
14 |
15 | # About [BlurHash](http://blurha.sh)
16 |
17 | BlurHash is a compact representation of a placeholder for an image.
18 |
19 | Learn More from the [BlurHash Repo](https://github.com/woltapp/blurhash.git)
20 |
21 | ### Why would you want this?
22 |
23 | Does your designer cry every time you load their beautifully designed screen, and it is full of empty boxes because all the
24 | images have not loaded yet? Does your database engineer cry when you want to solve this by trying to cram little thumbnail
25 | images into your data to show as placeholders?
26 |
27 | BlurHash will solve your problems! How? Like this:
28 |
29 | 
30 |
31 |
32 | You can also see nice examples and try it out yourself at [blurha.sh](http://blurha.sh/)!
33 | Learn More from the [BlurHash Repo](https://github.com/woltapp/blurhash.git)
34 |
35 | # Usage
36 | There are two ways you can use this package:
37 | 1. Using Async Helpers
38 | ```swift
39 | // Encoding
40 | let blurHashStr = await UnifiedBlurHash.getBlurHashString(from: image)
41 |
42 | // Decoding
43 | let image = await UnifiedBlurHash.getUnifiedImage(from: imageString)
44 |
45 | ```
46 |
47 | 2. Using Methods Directly
48 |
49 | ```swift
50 |
51 | // scale down for performance then encode
52 | let blurHashStr = unifiedImage.small?.blurHash(numberOfComponents: (4,4))
53 |
54 | // Decoding. Use small size and resize in UI for performance
55 | let image = UnifiedImage(blurHash: blurHashString, size: .init(width: 32, height: 32))
56 |
57 | ```
58 |
59 | # SwiftUI Examples
60 |
61 | ```swift
62 |
63 | import SwiftUI
64 | import UnifiedBlurHash
65 |
66 | struct ContentView: View {
67 | @State private var imageString = "LVN^Odxa?WNa-;WB£,WBs;baR*af"
68 | @State private var image: UnifiedImage? = nil
69 |
70 | var body: some View {
71 | VStack(spacing: 20) {
72 |
73 | Button("Encode", action: encode)
74 |
75 | Button("Decode", action: decode)
76 |
77 | if image != nil {
78 | Image(unifiedImage: image!)
79 | .resizable()
80 | }
81 | }
82 | .padding(.horizontal)
83 | }
84 |
85 | private func encode() {
86 | Task {
87 | let image = UnifiedImage(named: "sunflower")
88 | guard let image = image else {
89 | return
90 | }
91 | let str = await UnifiedBlurHash.getBlurHashString(from: image)
92 | guard let str = str else {
93 | return
94 | }
95 | DispatchQueue.main.async {
96 | self.imageString = str
97 | }
98 | }
99 | }
100 |
101 | private func decode() {
102 | Task {
103 | let image = await UnifiedBlurHash.getUnifiedImage(from: imageString)
104 | guard let image = image else {
105 | return
106 | }
107 | DispatchQueue.main.async {
108 | self.image = image
109 | }
110 | }
111 | }
112 | }
113 | ```
114 |
115 | or
116 |
117 | ```swift
118 |
119 | import SwiftUI
120 | import UnifiedBlurHash
121 |
122 | struct ContentView: View {
123 | var blurHash: String = "LVN^Odxa?WNa-;WB£,WBs;baR*af"
124 |
125 | var body: some View {
126 | Image(blurHash: blurHash)?
127 | .resizable()
128 | }
129 | }
130 |
131 | ```
132 | Under the hood `UnifiedImage` is just `UIImage` or `NSImage` depending on the platform.
133 |
134 | # License
135 | MIT
136 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Math.swift
3 | // UnifiedBlurHash
4 | //
5 | // Created by ian on 03/05/2025.
6 | //
7 |
8 | import Foundation
9 |
10 | enum Math {
11 | @inline(__always)
12 | static func signPow(_ value: Float, _ exp: Float) -> Float {
13 | return copysign(pow(abs(value), exp), value)
14 | }
15 |
16 | @inline(__always)
17 | static func linearTosRGB(_ value: Float) -> Int {
18 | let v = max(0, min(1, value))
19 | if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) }
20 | else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) }
21 | }
22 |
23 | @inline(__always)
24 | static func sRGBToLinear(_ value: Type) -> Float {
25 | let v = Float(Int64(value)) / 255
26 | if v <= 0.04045 { return v / 12.92 }
27 | else { return pow((v + 0.055) / 1.055, 2.4) }
28 | }
29 |
30 | // Precomputed encode characters for faster access
31 | static let encodeCharacters: [Character] = {
32 | return Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~")
33 | }()
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/Resources/sunflower.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iankoex/UnifiedBlurHash/675d79835d6338e3fd4a314ef3220ae41f7eb6d4/Sources/UnifiedBlurHash/Resources/sunflower.jpg
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/SwiftUI.Image+BlurHash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUI.Image+BlurHash.swift
3 | //
4 | //
5 | // Created by Ian on 13/12/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// A convenience initializer for creating a SwiftUI `Image` from a [BlurHash](https://blurha.sh/) string.
11 | ///
12 | /// This initializer attempts to decode a BlurHash string into an image of a specified size and with a punch factor for contrast enhancement.
13 | ///
14 | /// - Parameters:
15 | /// - blurHash: A non-empty `String` containing the BlurHash to decode. The string should conform to the BlurHash specification.
16 | /// - size: A `CGSize` specifying the desired dimensions of the resulting image. Defaults to 32x32 pixels if not specified.
17 | /// - punch: A `Float` used to adjust the contrast of the decoded image. Values greater than 1.0 increase contrast. Default is 1.
18 | ///
19 | /// - Returns: An optional `Image`. Returns `nil` if the BlurHash string is invalid or decoding fails.
20 | ///
21 | /// - Important:
22 | /// - This initializer is *failable* and will return `nil` if decoding the BlurHash string does not result in a valid image.
23 | /// - Ensure the `blurHash` string is valid and not empty to avoid a `nil` result.
24 | ///
25 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
26 | public extension Image {
27 | /// Initializes an `Image` from a BlurHash string.
28 | ///
29 | /// If the decoding process fails or returns an invalid image, the initializer returns `nil`.
30 | ///
31 | /// - Parameters:
32 | /// - blurHash: The BlurHash-encoded string.
33 | /// - size: The desired output image size. Default is 32x32.
34 | /// - punch: A contrast adjustment factor. Default is 1.
35 | init?(blurHash: String, size: CGSize = CGSize(width: 32, height: 32), punch: Float = 1) {
36 | let unifiedImg = UnifiedImage(blurHash: blurHash, size: size, punch: punch)
37 | guard let unifiedImg = unifiedImg else {
38 | return nil
39 | }
40 | self.init(unifiedImage: unifiedImg)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/UnifiedBlurHash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedBlurHash.swift
3 | //
4 | //
5 | // Created by Ian on 12/12/2022.
6 | //
7 | // Many Thanks to Mortenjust for https://github.com/mortenjust/Blurhash-macos.git
8 |
9 | import SwiftUI
10 |
11 | /// A utility struct for working with BlurHash-encoded images and operations related to encoding/decoding BlurHashes.
12 | ///
13 | /// Provides asynchronous methods to:
14 | /// - Generate a BlurHash string from a `UnifiedImage`.
15 | /// - Create a `UnifiedImage` from a BlurHash string.
16 | /// - Decode the average color from a BlurHash string.
17 | ///
18 | public struct UnifiedBlurHash {
19 |
20 | /// Initializes a new instance of `UnifiedBlurHash`.
21 | public init() {}
22 |
23 | /// Generates a BlurHash string from the provided `UnifiedImage`.
24 | ///
25 | /// - Parameter unifiedImage: The `UnifiedImage` instance to encode.
26 | /// - Returns: An optional `String` containing the BlurHash, or `nil` if the image cannot be encoded (e.g., `unifiedImage.small` is nil).
27 | ///
28 | /// - Important: Uses `(4, 3)` as the number of components for BlurHash encoding.
29 | ///
30 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
31 | public static func getBlurHashString(from unifiedImage: UnifiedImage) async -> String? {
32 | unifiedImage.small?.blurHash(numberOfComponents: (4, 3))
33 | }
34 |
35 | /// Decodes a BlurHash string into a `UnifiedImage`.
36 | ///
37 | /// - Parameter blurHashString: The BlurHash string to decode.
38 | /// - Returns: A `UnifiedImage` created from the given BlurHash, or `nil` if decoding fails.
39 | ///
40 | /// - Note: Uses a fixed output image size of 32x32 pixels.
41 | ///
42 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
43 | public static func getUnifiedImage(from blurHashString: String) async -> UnifiedImage? {
44 | UnifiedImage(blurHash: blurHashString, size: .init(width: 32, height: 32))
45 | }
46 |
47 | /// Computes the average color of an image represented by a BlurHash string.
48 | ///
49 | /// - Parameter blurHashString: The BlurHash string to analyze.
50 | /// - Returns: A `Color` representing the average color in the decoded image.
51 | ///
52 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
53 | public static func getAverageColor(from blurHashString: String) async -> Color {
54 | decodeAverageColor(blurHash: blurHashString)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/UnifiedImage+Decode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedImage+Decode.swift
3 | //
4 | //
5 | // Created by Ian on 12/12/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension UnifiedImage {
11 | public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
12 | guard blurHash.count >= 6 else { return nil }
13 |
14 | let sizeFlag = String(blurHash[0]).decode83()
15 | let numY = (sizeFlag / 9) + 1
16 | let numX = (sizeFlag % 9) + 1
17 |
18 | let quantisedMaximumValue = String(blurHash[1]).decode83()
19 | let maximumValue = Float(quantisedMaximumValue + 1) / 166
20 |
21 | guard blurHash.count == 4 + 2 * numX * numY else { return nil }
22 |
23 | let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
24 | if i == 0 {
25 | let value = String(blurHash[2 ..< 6]).decode83()
26 | return decodeDC(value)
27 | } else {
28 | let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83()
29 | return decodeAC(value, maximumValue: maximumValue * punch)
30 | }
31 | }
32 |
33 | let width = Int(size.width)
34 | let height = Int(size.height)
35 | let bytesPerRow = width * 3
36 | guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
37 | CFDataSetLength(data, bytesPerRow * height)
38 | guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
39 |
40 | var y = 0
41 |
42 | let floatWidth = Float(width)
43 |
44 | let floatHeight = Float(height)
45 |
46 | while y < height {
47 | var x = 0
48 |
49 | let floatY : Float = Float(y)
50 |
51 | while x < width {
52 | var j = 0
53 |
54 | var r: Float = 0
55 | var g: Float = 0
56 | var b: Float = 0
57 |
58 | let floatX : Float = Float(x)
59 |
60 | while j < numY {
61 | var i = 0
62 |
63 | let floatJ : Float = Float(j)
64 |
65 | while i < numX {
66 | let basis = cos(Float.pi * floatX * Float(i) / floatWidth) * cos(Float.pi * floatY * floatJ / floatHeight)
67 | let colour = colours[i + j * numX]
68 | r += colour.0 * basis
69 | g += colour.1 * basis
70 | b += colour.2 * basis
71 |
72 | i += 1
73 | }
74 |
75 | j += 1
76 | }
77 |
78 | let intR = UInt8(Math.linearTosRGB(r))
79 | let intG = UInt8(Math.linearTosRGB(g))
80 | let intB = UInt8(Math.linearTosRGB(b))
81 |
82 | pixels[3 * x + 0 + y * bytesPerRow] = intR
83 | pixels[3 * x + 1 + y * bytesPerRow] = intG
84 | pixels[3 * x + 2 + y * bytesPerRow] = intB
85 |
86 | x += 1
87 | }
88 |
89 | y += 1
90 | }
91 |
92 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
93 |
94 | guard let provider = CGDataProvider(data: data) else { return nil }
95 | let cgImage = CGImage(
96 | width: width,
97 | height: height,
98 | bitsPerComponent: 8,
99 | bitsPerPixel: 24,
100 | bytesPerRow: bytesPerRow,
101 | space: CGColorSpaceCreateDeviceRGB(),
102 | bitmapInfo: bitmapInfo,
103 | provider: provider,
104 | decode: nil,
105 | shouldInterpolate: true,
106 | intent: .defaultIntent
107 | )
108 | guard let cgImage = cgImage else {
109 | return nil
110 | }
111 |
112 | #if os (macOS)
113 | self.init(cgImage: cgImage, size: size)
114 | #endif
115 | #if canImport(UIKit)
116 | self.init(cgImage: cgImage)
117 | #endif
118 | }
119 | }
120 |
121 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
122 | func decodeAverageColor(blurHash: String) -> Color {
123 | let value = String(blurHash[2 ..< 6]).decode83()
124 | let intR = value >> 16
125 | let intG = (value >> 8) & 255
126 | let intB = value & 255
127 | return Color(red: Double(intR)/255, green: Double(intG)/255, blue: Double(intB)/255)
128 | }
129 |
130 | private func decodeDC(_ value: Int) -> (Float, Float, Float) {
131 | let intR = value >> 16
132 | let intG = (value >> 8) & 255
133 | let intB = value & 255
134 | return (Math.sRGBToLinear(intR), Math.sRGBToLinear(intG), Math.sRGBToLinear(intB))
135 | }
136 |
137 | private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
138 | let quantR = value / (19 * 19)
139 | let quantG = (value / 19) % 19
140 | let quantB = value % 19
141 |
142 | let rgb = (
143 | Math.signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
144 | Math.signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
145 | Math.signPow((Float(quantB) - 9) / 9, 2) * maximumValue
146 | )
147 |
148 | return rgb
149 | }
150 |
151 | private let decodeCharacters: [String: Int] = {
152 | var dict: [String: Int] = [:]
153 | for (index, character) in Math.encodeCharacters.map({ String($0) }).enumerated() {
154 | dict[character] = index
155 | }
156 | return dict
157 | }()
158 |
159 | extension String {
160 | func decode83() -> Int {
161 | var value: Int = 0
162 | for character in self {
163 | if let digit = decodeCharacters[String(character)] {
164 | value = value * 83 + digit
165 | }
166 | }
167 | return value
168 | }
169 | }
170 |
171 | private extension String {
172 | subscript (offset: Int) -> Character {
173 | return self[index(startIndex, offsetBy: offset)]
174 | }
175 |
176 | subscript (bounds: CountableClosedRange) -> Substring {
177 | let start = index(startIndex, offsetBy: bounds.lowerBound)
178 | let end = index(startIndex, offsetBy: bounds.upperBound)
179 | return self[start...end]
180 | }
181 |
182 | subscript (bounds: CountableRange) -> Substring {
183 | let start = index(startIndex, offsetBy: bounds.lowerBound)
184 | let end = index(startIndex, offsetBy: bounds.upperBound)
185 | return self[start.. String? {
18 | // Pre-calculate values that are used multiple times
19 | let width = Int(round(size.width))
20 | let height = Int(round(size.height))
21 | let size = CGSize(width: width, height: height)
22 |
23 | // Create context with optimized parameters
24 | guard let context = CGContext(
25 | data: nil,
26 | width: width,
27 | height: height,
28 | bitsPerComponent: 8,
29 | bytesPerRow: width * 4,
30 | space: CGColorSpaceCreateDeviceRGB(),
31 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
32 | ) else {
33 | return nil
34 | }
35 |
36 | context.scaleBy(x: 1, y: -1)
37 | context.translateBy(x: 0, y: -size.height)
38 |
39 | #if os(macOS)
40 | let prior = NSGraphicsContext.current
41 | NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: true)
42 | draw(in: CGRect(origin: .zero, size: size))
43 | NSGraphicsContext.current = prior
44 | #elseif os(iOS)
45 | UIGraphicsPushContext(context)
46 | draw(at: .zero)
47 | UIGraphicsPopContext()
48 | #endif
49 |
50 | guard let cgImage = context.makeImage(),
51 | let dataProvider = cgImage.dataProvider,
52 | let data = dataProvider.data,
53 | let pixels = CFDataGetBytePtr(data) else {
54 | return nil
55 | }
56 |
57 | let bytesPerRow = cgImage.bytesPerRow
58 | let bytesPerPixel = cgImage.bitsPerPixel / 8
59 | let pi = Float.pi
60 |
61 | // Pre-calculate component counts
62 | let componentX = components.0
63 | let componentY = components.1
64 | let totalComponents = componentX * componentY
65 |
66 | // Pre-allocate and reserve capacity for arrays
67 | var factors = [(Float, Float, Float)]()
68 | factors.reserveCapacity(totalComponents)
69 |
70 | // Pre-calculate width/height as Float to avoid repeated conversions
71 | let floatWidth = Float(width)
72 | let floatHeight = Float(height)
73 |
74 | // Calculate factors with optimized loops
75 | for y in 0..,
129 | width: Int,
130 | height: Int,
131 | bytesPerRow: Int,
132 | bytesPerPixel: Int,
133 | pixelOffset: Int,
134 | basisFunction: (Float, Float) -> Float
135 | ) -> (Float, Float, Float) {
136 | var r: Float = 0
137 | var g: Float = 0
138 | var b: Float = 0
139 |
140 | let height = height
141 | let width = width
142 | let bytesPerRow = bytesPerRow
143 | let bytesPerPixel = bytesPerPixel
144 |
145 | // Optimized pixel iteration
146 | for y in 0.. Int {
173 | let roundedR = Math.linearTosRGB(value.0)
174 | let roundedG = Math.linearTosRGB(value.1)
175 | let roundedB = Math.linearTosRGB(value.2)
176 | return (roundedR << 16) + (roundedG << 8) + roundedB
177 | }
178 |
179 | @inline(__always)
180 | private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int {
181 | let quantR = Int(max(0, min(18, floor(Math.signPow(value.0 / maximumValue, 0.5) * 9 + 9.5))))
182 | let quantG = Int(max(0, min(18, floor(Math.signPow(value.1 / maximumValue, 0.5) * 9 + 9.5))))
183 | let quantB = Int(max(0, min(18, floor(Math.signPow(value.2 / maximumValue, 0.5) * 9 + 9.5))))
184 |
185 | return quantR * 361 + quantG * 19 + quantB // 19*19 = 361
186 | }
187 |
188 | extension BinaryInteger {
189 | func encode83(length: Int) -> String {
190 | var result = ""
191 | result.reserveCapacity(length)
192 | var value = Int(self)
193 |
194 | for _ in 1...length {
195 | let digit = value % 83
196 | value /= 83
197 | result.append(Math.encodeCharacters[digit])
198 | }
199 |
200 | return String(result.reversed())
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/UnifiedImage+Scale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedImage+Scale.swift
3 | //
4 | //
5 | // Created by Ian on 12/12/2022.
6 | //
7 | import SwiftUI
8 |
9 | /// An extension to `UnifiedImage` that provides a computed property `small`
10 | /// to generate a resized image with a size of 32x32 pixels.
11 | public extension UnifiedImage {
12 |
13 | /// Returns a resized image with a size of 32x32 pixels.
14 | ///
15 | /// - Returns: A resized `UnifiedImage` if resizing is successful, `nil` otherwise.
16 | var small: UnifiedImage? {
17 | resized(to: CGSize(width: 32, height: 32))
18 | }
19 | }
20 |
21 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
22 | import AppKit
23 |
24 | public extension UnifiedImage {
25 |
26 | /// Resizes the current image to the specified `newSize`.
27 | ///
28 | /// - Parameter newSize: The desired size to resize the image to.
29 | /// - Returns: A new `UnifiedImage` resized to the provided size, or `nil` if resizing fails.
30 | func resized(to newSize: NSSize) -> UnifiedImage? {
31 | let image = NSImage(size: newSize)
32 | image.lockFocus()
33 | let context = NSGraphicsContext.current
34 | context?.imageInterpolation = .high
35 | draw(
36 | in: NSRect(origin: .zero, size: newSize),
37 | from: NSRect(origin: .zero, size: size),
38 | operation: .copy,
39 | fraction: 1
40 | )
41 | image.unlockFocus()
42 | return image
43 | }
44 | }
45 |
46 | #endif
47 |
48 | #if canImport(UIKit) && !os(watchOS)
49 | import UIKit
50 | import Foundation
51 |
52 | public extension UnifiedImage {
53 |
54 | /// Resizes the current image to the specified `newSize`.
55 | ///
56 | /// - Parameter newSize: The desired size to resize the image to.
57 | /// - Returns: A new `UnifiedImage` resized to the provided size, or `nil` if resizing fails.
58 | func resized(to newSize: CGSize) -> UnifiedImage? {
59 | let renderer = UIGraphicsImageRenderer(size: newSize)
60 |
61 | let image = renderer.image { (context) in
62 | self.draw(in: CGRect(origin: .zero, size: newSize))
63 | }
64 | return image
65 | }
66 | }
67 |
68 | #elseif os(watchOS)
69 |
70 | import UIKit
71 | import Foundation
72 |
73 | public extension UnifiedImage {
74 |
75 | /// Resizes the current image to the specified `newSize`.
76 | ///
77 | /// - Parameter newSize: The desired size to resize the image to.
78 | /// - Returns: A resized `UnifiedImage` or `nil` if resizing fails.
79 | func resized(to newSize: CGSize) -> UnifiedImage? {
80 | UIGraphicsBeginImageContextWithOptions(size, false, self.scale)
81 | defer { UIGraphicsEndImageContext() }
82 | draw(in: CGRect(origin: .zero, size: newSize))
83 | return UIGraphicsGetImageFromCurrentImageContext()
84 | }
85 | }
86 |
87 | #endif
88 |
--------------------------------------------------------------------------------
/Sources/UnifiedBlurHash/UnifiedImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedImage.swift
3 | //
4 | //
5 | // Created by Ian on 12/12/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | #if canImport(UIKit)
11 | import UIKit
12 |
13 | /// A typealias representing a platform-independent image.
14 | ///
15 | /// On iOS, tvOS, and watchOS, this maps to `UIImage`.
16 | public typealias UnifiedImage = UIImage
17 |
18 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
19 | public extension Image {
20 | /// Initializes a SwiftUI `Image` from a `UnifiedImage`.
21 | ///
22 | /// - Parameter unifiedImage: A `UIImage` to be wrapped in a SwiftUI `Image`.
23 | init(unifiedImage: UnifiedImage) {
24 | self.init(uiImage: unifiedImage)
25 | }
26 | }
27 | #endif
28 |
29 | #if os(macOS)
30 | import AppKit
31 |
32 | /// A typealias representing a platform-independent image.
33 | ///
34 | /// On macOS, this maps to `NSImage`.
35 | public typealias UnifiedImage = NSImage
36 |
37 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
38 | public extension Image {
39 | /// Initializes a SwiftUI `Image` from a `UnifiedImage`.
40 | ///
41 | /// - Parameter unifiedImage: An `NSImage` to be wrapped in a SwiftUI `Image`.
42 | init(unifiedImage: UnifiedImage) {
43 | self.init(nsImage: unifiedImage)
44 | }
45 | }
46 | #endif
47 |
--------------------------------------------------------------------------------
/Tests/UnifiedBlurHashTests/UnifiedBlurHashTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedBlurHashTests.swift
3 | // UnifiedBlurHash
4 | //
5 | // Created by ian on 3/9/25.
6 | //
7 |
8 | import Testing
9 | @testable import UnifiedBlurHash
10 | import Foundation
11 | import SwiftUI
12 |
13 | @Suite("UnifiedBlurHash Functionality Tests")
14 | class UnifiedBlurHashTests {
15 |
16 | @Test("Decode valid blur hash string into UnifiedImage")
17 | func testDecodeValidBlurHash() async {
18 | let blurHash = "LWC?S~x]Sjof~Wx]X9oe?btRofax" // Valid placeholder BlurHash
19 | let image = await UnifiedBlurHash.getUnifiedImage(from: blurHash)
20 |
21 | #expect(image != nil)
22 | #expect(image?.size.width == 32)
23 | #expect(image?.size.height == 32)
24 | }
25 |
26 | @Test("Get blur hash string from a valid UnifiedImage")
27 | func testEncodeUnifiedImageToBlurHash() async {
28 | guard let image = Bundle.module.image(forResource: "sunflower") else {
29 | #expect(Bool(false), "Test image not found in bundle")
30 | return
31 | }
32 | let hash = await UnifiedBlurHash.getBlurHashString(from: image)
33 | #expect(hash == "LWC?S~x]Sjof~Wx]X9oe?btRofax")
34 | #expect(hash != nil && hash!.count >= 6)
35 | }
36 |
37 | @Test("Get average color from valid BlurHash string")
38 | func testAverageColorFromBlurHash() async {
39 | let blurHash = "LWC?S~x]Sjof~Wx]X9oe?btRofax"
40 | let color = await UnifiedBlurHash.getAverageColor(from: blurHash)
41 |
42 | // You can’t compare Color directly, but we can at least ensure it's not black or clear.
43 | let components = color.description.lowercased()
44 | #expect(!components.contains("clear"))
45 | }
46 |
47 | @Test("Fail to decode invalid blur hash string")
48 | func testInvalidBlurHashDecoding() async {
49 | let invalidHash = "LWC" // Too short to be valid
50 | let image = await UnifiedBlurHash.getUnifiedImage(from: invalidHash)
51 | #expect(image == nil)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Tests/UnifiedBlurHashTests/UnifiedImageResizingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnifiedImageResizingTests.swift
3 | // UnifiedBlurHash
4 | //
5 | // Created by ian on 03/05/2025.
6 | //
7 |
8 |
9 | import Testing
10 | @testable import UnifiedBlurHash
11 | import Foundation
12 | import SwiftUI
13 |
14 | @Suite("UnifiedImage Resizing Tests")
15 | class UnifiedImageResizingTests {
16 |
17 | @Test("Resizing an image to 32x32 produces the expected size")
18 | func testResizingTo32x32() {
19 | // Create an example image with a known size
20 | guard let originalImage = Bundle.module.image(forResource: "sunflower") else {
21 | #expect(Bool(false), "Test image not found in bundle")
22 | return
23 | }
24 |
25 | // Resize the image to 32x32
26 | let resizedImage = originalImage.resized(to: CGSize(width: 32, height: 32))
27 |
28 | #expect(resizedImage?.size.width == 32)
29 | #expect(resizedImage?.size.height == 32)
30 | }
31 |
32 | @Test("Resizing returns nil for invalid image")
33 | func testResizingInvalidImage() {
34 | let invalidImage: UnifiedImage? = nil
35 |
36 | let resizedImage = invalidImage?.resized(to: CGSize(width: 32, height: 32))
37 |
38 | #expect(resizedImage == nil)
39 | }
40 |
41 | @Test("Resizing an image produces a non-nil result")
42 | func testResizingValidImage() {
43 | // Create a valid image from a system image
44 | guard let originalImage = Bundle.module.image(forResource: "sunflower") else {
45 | #expect(Bool(false), "Test image not found in bundle")
46 | return
47 | }
48 |
49 | // Resize the image
50 | let resizedImage = originalImage.resized(to: CGSize(width: 64, height: 64))
51 |
52 | #expect(resizedImage != nil)
53 | #expect(resizedImage?.size.width == 64)
54 | #expect(resizedImage?.size.height == 64)
55 | }
56 |
57 | @Test("Small property returns 32x32 resized image")
58 | func testSmallProperty() {
59 | // Create an example image with a known size
60 | guard let originalImage = Bundle.module.image(forResource: "sunflower") else {
61 | #expect(Bool(false), "Test image not found in bundle")
62 | return
63 | }
64 |
65 | // Use the `small` property to resize the image
66 | let smallImage = originalImage.small
67 |
68 | #expect(smallImage?.size.width == 32)
69 | #expect(smallImage?.size.height == 32)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/UnifiedBlurHashTests/Utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestUtilities.swift
3 | // UnifiedBlurHash
4 | //
5 | // Created by ian on 03/05/2025.
6 | //
7 |
8 | import Foundation
9 | import UnifiedBlurHash
10 |
11 | #if canImport(UIKit)
12 | import UIKit
13 |
14 | extension Bundle {
15 | func image(forResource name: String) -> UnifiedImage? {
16 | // guard let path = self.path(forResource: name, ofType: nil) else {
17 | // return nil
18 | // }
19 | return UIImage(named: name, in: self, compatibleWith: nil)
20 | }
21 | }
22 | #endif
23 |
--------------------------------------------------------------------------------
/demo/demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3C140DBF2A51952F00A8783E /* demoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140DBE2A51952F00A8783E /* demoApp.swift */; };
11 | 3C140DC12A51952F00A8783E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140DC02A51952F00A8783E /* ContentView.swift */; };
12 | 3C140DC32A51953100A8783E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C140DC22A51953100A8783E /* Assets.xcassets */; };
13 | 3C140DC72A51953100A8783E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C140DC62A51953100A8783E /* Preview Assets.xcassets */; };
14 | 3C140DD12A51953200A8783E /* demoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140DD02A51953200A8783E /* demoTests.swift */; };
15 | 3C140DDB2A51953200A8783E /* demoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140DDA2A51953200A8783E /* demoUITests.swift */; };
16 | 3C140DDD2A51953200A8783E /* demoUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140DDC2A51953200A8783E /* demoUITestsLaunchTests.swift */; };
17 | 3C140DED2A51960200A8783E /* UnifiedBlurHash in Frameworks */ = {isa = PBXBuildFile; productRef = 3C140DEC2A51960200A8783E /* UnifiedBlurHash */; };
18 | 3C140E2D2A51C81600A8783E /* watchOS_demoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140E2C2A51C81600A8783E /* watchOS_demoApp.swift */; };
19 | 3C140E2F2A51C81600A8783E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140E2E2A51C81600A8783E /* ContentView.swift */; };
20 | 3C140E312A51C81900A8783E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C140E302A51C81900A8783E /* Assets.xcassets */; };
21 | 3C140E342A51C81900A8783E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C140E332A51C81900A8783E /* Preview Assets.xcassets */; };
22 | 3C140E3E2A51C81A00A8783E /* watchOS_demo_Watch_AppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140E3D2A51C81A00A8783E /* watchOS_demo_Watch_AppTests.swift */; };
23 | 3C140E482A51C81A00A8783E /* watchOS_demo_Watch_AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140E472A51C81A00A8783E /* watchOS_demo_Watch_AppUITests.swift */; };
24 | 3C140E4A2A51C81A00A8783E /* watchOS_demo_Watch_AppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C140E492A51C81A00A8783E /* watchOS_demo_Watch_AppUITestsLaunchTests.swift */; };
25 | 3C140E4D2A51C81A00A8783E /* watchOS-demo Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 3C140E2A2A51C81600A8783E /* watchOS-demo Watch App.app */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
26 | 3C140E5B2A51C8CC00A8783E /* UnifiedBlurHash in Frameworks */ = {isa = PBXBuildFile; productRef = 3C140E5A2A51C8CC00A8783E /* UnifiedBlurHash */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXContainerItemProxy section */
30 | 3C140DCD2A51953200A8783E /* PBXContainerItemProxy */ = {
31 | isa = PBXContainerItemProxy;
32 | containerPortal = 3C140DB32A51952F00A8783E /* Project object */;
33 | proxyType = 1;
34 | remoteGlobalIDString = 3C140DBA2A51952F00A8783E;
35 | remoteInfo = demo;
36 | };
37 | 3C140DD72A51953200A8783E /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = 3C140DB32A51952F00A8783E /* Project object */;
40 | proxyType = 1;
41 | remoteGlobalIDString = 3C140DBA2A51952F00A8783E;
42 | remoteInfo = demo;
43 | };
44 | 3C140E3A2A51C81900A8783E /* PBXContainerItemProxy */ = {
45 | isa = PBXContainerItemProxy;
46 | containerPortal = 3C140DB32A51952F00A8783E /* Project object */;
47 | proxyType = 1;
48 | remoteGlobalIDString = 3C140E292A51C81600A8783E;
49 | remoteInfo = "watchOS-demo Watch App";
50 | };
51 | 3C140E442A51C81A00A8783E /* PBXContainerItemProxy */ = {
52 | isa = PBXContainerItemProxy;
53 | containerPortal = 3C140DB32A51952F00A8783E /* Project object */;
54 | proxyType = 1;
55 | remoteGlobalIDString = 3C140E292A51C81600A8783E;
56 | remoteInfo = "watchOS-demo Watch App";
57 | };
58 | 3C140E4B2A51C81A00A8783E /* PBXContainerItemProxy */ = {
59 | isa = PBXContainerItemProxy;
60 | containerPortal = 3C140DB32A51952F00A8783E /* Project object */;
61 | proxyType = 1;
62 | remoteGlobalIDString = 3C140E292A51C81600A8783E;
63 | remoteInfo = "watchOS-demo Watch App";
64 | };
65 | /* End PBXContainerItemProxy section */
66 |
67 | /* Begin PBXCopyFilesBuildPhase section */
68 | 3C140E512A51C81B00A8783E /* Embed Watch Content */ = {
69 | isa = PBXCopyFilesBuildPhase;
70 | buildActionMask = 2147483647;
71 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
72 | dstSubfolderSpec = 16;
73 | files = (
74 | 3C140E4D2A51C81A00A8783E /* watchOS-demo Watch App.app in Embed Watch Content */,
75 | );
76 | name = "Embed Watch Content";
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | /* End PBXCopyFilesBuildPhase section */
80 |
81 | /* Begin PBXFileReference section */
82 | 3C140DBB2A51952F00A8783E /* demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
83 | 3C140DBE2A51952F00A8783E /* demoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = demoApp.swift; sourceTree = ""; };
84 | 3C140DC02A51952F00A8783E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
85 | 3C140DC22A51953100A8783E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
86 | 3C140DC42A51953100A8783E /* demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = demo.entitlements; sourceTree = ""; };
87 | 3C140DC62A51953100A8783E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
88 | 3C140DCC2A51953200A8783E /* demoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = demoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
89 | 3C140DD02A51953200A8783E /* demoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = demoTests.swift; sourceTree = ""; };
90 | 3C140DD62A51953200A8783E /* demoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = demoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
91 | 3C140DDA2A51953200A8783E /* demoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = demoUITests.swift; sourceTree = ""; };
92 | 3C140DDC2A51953200A8783E /* demoUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = demoUITestsLaunchTests.swift; sourceTree = ""; };
93 | 3C140DEA2A5195DF00A8783E /* UnifiedBlurHash */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UnifiedBlurHash; path = ..; sourceTree = ""; };
94 | 3C140E2A2A51C81600A8783E /* watchOS-demo Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "watchOS-demo Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
95 | 3C140E2C2A51C81600A8783E /* watchOS_demoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = watchOS_demoApp.swift; sourceTree = ""; };
96 | 3C140E2E2A51C81600A8783E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
97 | 3C140E302A51C81900A8783E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
98 | 3C140E332A51C81900A8783E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
99 | 3C140E392A51C81900A8783E /* watchOS-demo Watch AppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "watchOS-demo Watch AppTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
100 | 3C140E3D2A51C81A00A8783E /* watchOS_demo_Watch_AppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = watchOS_demo_Watch_AppTests.swift; sourceTree = ""; };
101 | 3C140E432A51C81A00A8783E /* watchOS-demo Watch AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "watchOS-demo Watch AppUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
102 | 3C140E472A51C81A00A8783E /* watchOS_demo_Watch_AppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = watchOS_demo_Watch_AppUITests.swift; sourceTree = ""; };
103 | 3C140E492A51C81A00A8783E /* watchOS_demo_Watch_AppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = watchOS_demo_Watch_AppUITestsLaunchTests.swift; sourceTree = ""; };
104 | /* End PBXFileReference section */
105 |
106 | /* Begin PBXFrameworksBuildPhase section */
107 | 3C140DB82A51952F00A8783E /* Frameworks */ = {
108 | isa = PBXFrameworksBuildPhase;
109 | buildActionMask = 2147483647;
110 | files = (
111 | 3C140DED2A51960200A8783E /* UnifiedBlurHash in Frameworks */,
112 | );
113 | runOnlyForDeploymentPostprocessing = 0;
114 | };
115 | 3C140DC92A51953200A8783E /* Frameworks */ = {
116 | isa = PBXFrameworksBuildPhase;
117 | buildActionMask = 2147483647;
118 | files = (
119 | );
120 | runOnlyForDeploymentPostprocessing = 0;
121 | };
122 | 3C140DD32A51953200A8783E /* Frameworks */ = {
123 | isa = PBXFrameworksBuildPhase;
124 | buildActionMask = 2147483647;
125 | files = (
126 | );
127 | runOnlyForDeploymentPostprocessing = 0;
128 | };
129 | 3C140E272A51C81600A8783E /* Frameworks */ = {
130 | isa = PBXFrameworksBuildPhase;
131 | buildActionMask = 2147483647;
132 | files = (
133 | 3C140E5B2A51C8CC00A8783E /* UnifiedBlurHash in Frameworks */,
134 | );
135 | runOnlyForDeploymentPostprocessing = 0;
136 | };
137 | 3C140E362A51C81900A8783E /* Frameworks */ = {
138 | isa = PBXFrameworksBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | );
142 | runOnlyForDeploymentPostprocessing = 0;
143 | };
144 | 3C140E402A51C81A00A8783E /* Frameworks */ = {
145 | isa = PBXFrameworksBuildPhase;
146 | buildActionMask = 2147483647;
147 | files = (
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXFrameworksBuildPhase section */
152 |
153 | /* Begin PBXGroup section */
154 | 3C140DB22A51952F00A8783E = {
155 | isa = PBXGroup;
156 | children = (
157 | 3C140DE92A5195DF00A8783E /* Packages */,
158 | 3C140DBD2A51952F00A8783E /* demo */,
159 | 3C140DCF2A51953200A8783E /* demoTests */,
160 | 3C140DD92A51953200A8783E /* demoUITests */,
161 | 3C140E2B2A51C81600A8783E /* watchOS-demo Watch App */,
162 | 3C140E3C2A51C81A00A8783E /* watchOS-demo Watch AppTests */,
163 | 3C140E462A51C81A00A8783E /* watchOS-demo Watch AppUITests */,
164 | 3C140DBC2A51952F00A8783E /* Products */,
165 | 3C140DEB2A51960200A8783E /* Frameworks */,
166 | );
167 | sourceTree = "";
168 | };
169 | 3C140DBC2A51952F00A8783E /* Products */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 3C140DBB2A51952F00A8783E /* demo.app */,
173 | 3C140DCC2A51953200A8783E /* demoTests.xctest */,
174 | 3C140DD62A51953200A8783E /* demoUITests.xctest */,
175 | 3C140E2A2A51C81600A8783E /* watchOS-demo Watch App.app */,
176 | 3C140E392A51C81900A8783E /* watchOS-demo Watch AppTests.xctest */,
177 | 3C140E432A51C81A00A8783E /* watchOS-demo Watch AppUITests.xctest */,
178 | );
179 | name = Products;
180 | sourceTree = "";
181 | };
182 | 3C140DBD2A51952F00A8783E /* demo */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 3C140DBE2A51952F00A8783E /* demoApp.swift */,
186 | 3C140DC02A51952F00A8783E /* ContentView.swift */,
187 | 3C140DC22A51953100A8783E /* Assets.xcassets */,
188 | 3C140DC42A51953100A8783E /* demo.entitlements */,
189 | 3C140DC52A51953100A8783E /* Preview Content */,
190 | );
191 | path = demo;
192 | sourceTree = "";
193 | };
194 | 3C140DC52A51953100A8783E /* Preview Content */ = {
195 | isa = PBXGroup;
196 | children = (
197 | 3C140DC62A51953100A8783E /* Preview Assets.xcassets */,
198 | );
199 | path = "Preview Content";
200 | sourceTree = "";
201 | };
202 | 3C140DCF2A51953200A8783E /* demoTests */ = {
203 | isa = PBXGroup;
204 | children = (
205 | 3C140DD02A51953200A8783E /* demoTests.swift */,
206 | );
207 | path = demoTests;
208 | sourceTree = "";
209 | };
210 | 3C140DD92A51953200A8783E /* demoUITests */ = {
211 | isa = PBXGroup;
212 | children = (
213 | 3C140DDA2A51953200A8783E /* demoUITests.swift */,
214 | 3C140DDC2A51953200A8783E /* demoUITestsLaunchTests.swift */,
215 | );
216 | path = demoUITests;
217 | sourceTree = "";
218 | };
219 | 3C140DE92A5195DF00A8783E /* Packages */ = {
220 | isa = PBXGroup;
221 | children = (
222 | 3C140DEA2A5195DF00A8783E /* UnifiedBlurHash */,
223 | );
224 | name = Packages;
225 | sourceTree = "";
226 | };
227 | 3C140DEB2A51960200A8783E /* Frameworks */ = {
228 | isa = PBXGroup;
229 | children = (
230 | );
231 | name = Frameworks;
232 | sourceTree = "";
233 | };
234 | 3C140E2B2A51C81600A8783E /* watchOS-demo Watch App */ = {
235 | isa = PBXGroup;
236 | children = (
237 | 3C140E2C2A51C81600A8783E /* watchOS_demoApp.swift */,
238 | 3C140E2E2A51C81600A8783E /* ContentView.swift */,
239 | 3C140E302A51C81900A8783E /* Assets.xcassets */,
240 | 3C140E322A51C81900A8783E /* Preview Content */,
241 | );
242 | path = "watchOS-demo Watch App";
243 | sourceTree = "";
244 | };
245 | 3C140E322A51C81900A8783E /* Preview Content */ = {
246 | isa = PBXGroup;
247 | children = (
248 | 3C140E332A51C81900A8783E /* Preview Assets.xcassets */,
249 | );
250 | path = "Preview Content";
251 | sourceTree = "";
252 | };
253 | 3C140E3C2A51C81A00A8783E /* watchOS-demo Watch AppTests */ = {
254 | isa = PBXGroup;
255 | children = (
256 | 3C140E3D2A51C81A00A8783E /* watchOS_demo_Watch_AppTests.swift */,
257 | );
258 | path = "watchOS-demo Watch AppTests";
259 | sourceTree = "";
260 | };
261 | 3C140E462A51C81A00A8783E /* watchOS-demo Watch AppUITests */ = {
262 | isa = PBXGroup;
263 | children = (
264 | 3C140E472A51C81A00A8783E /* watchOS_demo_Watch_AppUITests.swift */,
265 | 3C140E492A51C81A00A8783E /* watchOS_demo_Watch_AppUITestsLaunchTests.swift */,
266 | );
267 | path = "watchOS-demo Watch AppUITests";
268 | sourceTree = "";
269 | };
270 | /* End PBXGroup section */
271 |
272 | /* Begin PBXNativeTarget section */
273 | 3C140DBA2A51952F00A8783E /* demo */ = {
274 | isa = PBXNativeTarget;
275 | buildConfigurationList = 3C140DE02A51953200A8783E /* Build configuration list for PBXNativeTarget "demo" */;
276 | buildPhases = (
277 | 3C140DB72A51952F00A8783E /* Sources */,
278 | 3C140DB82A51952F00A8783E /* Frameworks */,
279 | 3C140DB92A51952F00A8783E /* Resources */,
280 | 3C140E512A51C81B00A8783E /* Embed Watch Content */,
281 | );
282 | buildRules = (
283 | );
284 | dependencies = (
285 | 3C140E4C2A51C81A00A8783E /* PBXTargetDependency */,
286 | );
287 | name = demo;
288 | packageProductDependencies = (
289 | 3C140DEC2A51960200A8783E /* UnifiedBlurHash */,
290 | );
291 | productName = demo;
292 | productReference = 3C140DBB2A51952F00A8783E /* demo.app */;
293 | productType = "com.apple.product-type.application";
294 | };
295 | 3C140DCB2A51953200A8783E /* demoTests */ = {
296 | isa = PBXNativeTarget;
297 | buildConfigurationList = 3C140DE32A51953200A8783E /* Build configuration list for PBXNativeTarget "demoTests" */;
298 | buildPhases = (
299 | 3C140DC82A51953200A8783E /* Sources */,
300 | 3C140DC92A51953200A8783E /* Frameworks */,
301 | 3C140DCA2A51953200A8783E /* Resources */,
302 | );
303 | buildRules = (
304 | );
305 | dependencies = (
306 | 3C140DCE2A51953200A8783E /* PBXTargetDependency */,
307 | );
308 | name = demoTests;
309 | productName = demoTests;
310 | productReference = 3C140DCC2A51953200A8783E /* demoTests.xctest */;
311 | productType = "com.apple.product-type.bundle.unit-test";
312 | };
313 | 3C140DD52A51953200A8783E /* demoUITests */ = {
314 | isa = PBXNativeTarget;
315 | buildConfigurationList = 3C140DE62A51953200A8783E /* Build configuration list for PBXNativeTarget "demoUITests" */;
316 | buildPhases = (
317 | 3C140DD22A51953200A8783E /* Sources */,
318 | 3C140DD32A51953200A8783E /* Frameworks */,
319 | 3C140DD42A51953200A8783E /* Resources */,
320 | );
321 | buildRules = (
322 | );
323 | dependencies = (
324 | 3C140DD82A51953200A8783E /* PBXTargetDependency */,
325 | );
326 | name = demoUITests;
327 | productName = demoUITests;
328 | productReference = 3C140DD62A51953200A8783E /* demoUITests.xctest */;
329 | productType = "com.apple.product-type.bundle.ui-testing";
330 | };
331 | 3C140E292A51C81600A8783E /* watchOS-demo Watch App */ = {
332 | isa = PBXNativeTarget;
333 | buildConfigurationList = 3C140E4E2A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch App" */;
334 | buildPhases = (
335 | 3C140E262A51C81600A8783E /* Sources */,
336 | 3C140E272A51C81600A8783E /* Frameworks */,
337 | 3C140E282A51C81600A8783E /* Resources */,
338 | );
339 | buildRules = (
340 | );
341 | dependencies = (
342 | );
343 | name = "watchOS-demo Watch App";
344 | packageProductDependencies = (
345 | 3C140E5A2A51C8CC00A8783E /* UnifiedBlurHash */,
346 | );
347 | productName = "watchOS-demo Watch App";
348 | productReference = 3C140E2A2A51C81600A8783E /* watchOS-demo Watch App.app */;
349 | productType = "com.apple.product-type.application";
350 | };
351 | 3C140E382A51C81900A8783E /* watchOS-demo Watch AppTests */ = {
352 | isa = PBXNativeTarget;
353 | buildConfigurationList = 3C140E522A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch AppTests" */;
354 | buildPhases = (
355 | 3C140E352A51C81900A8783E /* Sources */,
356 | 3C140E362A51C81900A8783E /* Frameworks */,
357 | 3C140E372A51C81900A8783E /* Resources */,
358 | );
359 | buildRules = (
360 | );
361 | dependencies = (
362 | 3C140E3B2A51C81900A8783E /* PBXTargetDependency */,
363 | );
364 | name = "watchOS-demo Watch AppTests";
365 | productName = "watchOS-demo Watch AppTests";
366 | productReference = 3C140E392A51C81900A8783E /* watchOS-demo Watch AppTests.xctest */;
367 | productType = "com.apple.product-type.bundle.unit-test";
368 | };
369 | 3C140E422A51C81A00A8783E /* watchOS-demo Watch AppUITests */ = {
370 | isa = PBXNativeTarget;
371 | buildConfigurationList = 3C140E552A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch AppUITests" */;
372 | buildPhases = (
373 | 3C140E3F2A51C81A00A8783E /* Sources */,
374 | 3C140E402A51C81A00A8783E /* Frameworks */,
375 | 3C140E412A51C81A00A8783E /* Resources */,
376 | );
377 | buildRules = (
378 | );
379 | dependencies = (
380 | 3C140E452A51C81A00A8783E /* PBXTargetDependency */,
381 | );
382 | name = "watchOS-demo Watch AppUITests";
383 | productName = "watchOS-demo Watch AppUITests";
384 | productReference = 3C140E432A51C81A00A8783E /* watchOS-demo Watch AppUITests.xctest */;
385 | productType = "com.apple.product-type.bundle.ui-testing";
386 | };
387 | /* End PBXNativeTarget section */
388 |
389 | /* Begin PBXProject section */
390 | 3C140DB32A51952F00A8783E /* Project object */ = {
391 | isa = PBXProject;
392 | attributes = {
393 | BuildIndependentTargetsInParallel = 1;
394 | LastSwiftUpdateCheck = 1430;
395 | LastUpgradeCheck = 1430;
396 | TargetAttributes = {
397 | 3C140DBA2A51952F00A8783E = {
398 | CreatedOnToolsVersion = 14.3;
399 | };
400 | 3C140DCB2A51953200A8783E = {
401 | CreatedOnToolsVersion = 14.3;
402 | TestTargetID = 3C140DBA2A51952F00A8783E;
403 | };
404 | 3C140DD52A51953200A8783E = {
405 | CreatedOnToolsVersion = 14.3;
406 | TestTargetID = 3C140DBA2A51952F00A8783E;
407 | };
408 | 3C140E292A51C81600A8783E = {
409 | CreatedOnToolsVersion = 14.3;
410 | };
411 | 3C140E382A51C81900A8783E = {
412 | CreatedOnToolsVersion = 14.3;
413 | TestTargetID = 3C140E292A51C81600A8783E;
414 | };
415 | 3C140E422A51C81A00A8783E = {
416 | CreatedOnToolsVersion = 14.3;
417 | TestTargetID = 3C140E292A51C81600A8783E;
418 | };
419 | };
420 | };
421 | buildConfigurationList = 3C140DB62A51952F00A8783E /* Build configuration list for PBXProject "demo" */;
422 | compatibilityVersion = "Xcode 14.0";
423 | developmentRegion = en;
424 | hasScannedForEncodings = 0;
425 | knownRegions = (
426 | en,
427 | Base,
428 | );
429 | mainGroup = 3C140DB22A51952F00A8783E;
430 | productRefGroup = 3C140DBC2A51952F00A8783E /* Products */;
431 | projectDirPath = "";
432 | projectRoot = "";
433 | targets = (
434 | 3C140DBA2A51952F00A8783E /* demo */,
435 | 3C140DCB2A51953200A8783E /* demoTests */,
436 | 3C140DD52A51953200A8783E /* demoUITests */,
437 | 3C140E292A51C81600A8783E /* watchOS-demo Watch App */,
438 | 3C140E382A51C81900A8783E /* watchOS-demo Watch AppTests */,
439 | 3C140E422A51C81A00A8783E /* watchOS-demo Watch AppUITests */,
440 | );
441 | };
442 | /* End PBXProject section */
443 |
444 | /* Begin PBXResourcesBuildPhase section */
445 | 3C140DB92A51952F00A8783E /* Resources */ = {
446 | isa = PBXResourcesBuildPhase;
447 | buildActionMask = 2147483647;
448 | files = (
449 | 3C140DC72A51953100A8783E /* Preview Assets.xcassets in Resources */,
450 | 3C140DC32A51953100A8783E /* Assets.xcassets in Resources */,
451 | );
452 | runOnlyForDeploymentPostprocessing = 0;
453 | };
454 | 3C140DCA2A51953200A8783E /* Resources */ = {
455 | isa = PBXResourcesBuildPhase;
456 | buildActionMask = 2147483647;
457 | files = (
458 | );
459 | runOnlyForDeploymentPostprocessing = 0;
460 | };
461 | 3C140DD42A51953200A8783E /* Resources */ = {
462 | isa = PBXResourcesBuildPhase;
463 | buildActionMask = 2147483647;
464 | files = (
465 | );
466 | runOnlyForDeploymentPostprocessing = 0;
467 | };
468 | 3C140E282A51C81600A8783E /* Resources */ = {
469 | isa = PBXResourcesBuildPhase;
470 | buildActionMask = 2147483647;
471 | files = (
472 | 3C140E342A51C81900A8783E /* Preview Assets.xcassets in Resources */,
473 | 3C140E312A51C81900A8783E /* Assets.xcassets in Resources */,
474 | );
475 | runOnlyForDeploymentPostprocessing = 0;
476 | };
477 | 3C140E372A51C81900A8783E /* Resources */ = {
478 | isa = PBXResourcesBuildPhase;
479 | buildActionMask = 2147483647;
480 | files = (
481 | );
482 | runOnlyForDeploymentPostprocessing = 0;
483 | };
484 | 3C140E412A51C81A00A8783E /* Resources */ = {
485 | isa = PBXResourcesBuildPhase;
486 | buildActionMask = 2147483647;
487 | files = (
488 | );
489 | runOnlyForDeploymentPostprocessing = 0;
490 | };
491 | /* End PBXResourcesBuildPhase section */
492 |
493 | /* Begin PBXSourcesBuildPhase section */
494 | 3C140DB72A51952F00A8783E /* Sources */ = {
495 | isa = PBXSourcesBuildPhase;
496 | buildActionMask = 2147483647;
497 | files = (
498 | 3C140DC12A51952F00A8783E /* ContentView.swift in Sources */,
499 | 3C140DBF2A51952F00A8783E /* demoApp.swift in Sources */,
500 | );
501 | runOnlyForDeploymentPostprocessing = 0;
502 | };
503 | 3C140DC82A51953200A8783E /* Sources */ = {
504 | isa = PBXSourcesBuildPhase;
505 | buildActionMask = 2147483647;
506 | files = (
507 | 3C140DD12A51953200A8783E /* demoTests.swift in Sources */,
508 | );
509 | runOnlyForDeploymentPostprocessing = 0;
510 | };
511 | 3C140DD22A51953200A8783E /* Sources */ = {
512 | isa = PBXSourcesBuildPhase;
513 | buildActionMask = 2147483647;
514 | files = (
515 | 3C140DDB2A51953200A8783E /* demoUITests.swift in Sources */,
516 | 3C140DDD2A51953200A8783E /* demoUITestsLaunchTests.swift in Sources */,
517 | );
518 | runOnlyForDeploymentPostprocessing = 0;
519 | };
520 | 3C140E262A51C81600A8783E /* Sources */ = {
521 | isa = PBXSourcesBuildPhase;
522 | buildActionMask = 2147483647;
523 | files = (
524 | 3C140E2F2A51C81600A8783E /* ContentView.swift in Sources */,
525 | 3C140E2D2A51C81600A8783E /* watchOS_demoApp.swift in Sources */,
526 | );
527 | runOnlyForDeploymentPostprocessing = 0;
528 | };
529 | 3C140E352A51C81900A8783E /* Sources */ = {
530 | isa = PBXSourcesBuildPhase;
531 | buildActionMask = 2147483647;
532 | files = (
533 | 3C140E3E2A51C81A00A8783E /* watchOS_demo_Watch_AppTests.swift in Sources */,
534 | );
535 | runOnlyForDeploymentPostprocessing = 0;
536 | };
537 | 3C140E3F2A51C81A00A8783E /* Sources */ = {
538 | isa = PBXSourcesBuildPhase;
539 | buildActionMask = 2147483647;
540 | files = (
541 | 3C140E482A51C81A00A8783E /* watchOS_demo_Watch_AppUITests.swift in Sources */,
542 | 3C140E4A2A51C81A00A8783E /* watchOS_demo_Watch_AppUITestsLaunchTests.swift in Sources */,
543 | );
544 | runOnlyForDeploymentPostprocessing = 0;
545 | };
546 | /* End PBXSourcesBuildPhase section */
547 |
548 | /* Begin PBXTargetDependency section */
549 | 3C140DCE2A51953200A8783E /* PBXTargetDependency */ = {
550 | isa = PBXTargetDependency;
551 | target = 3C140DBA2A51952F00A8783E /* demo */;
552 | targetProxy = 3C140DCD2A51953200A8783E /* PBXContainerItemProxy */;
553 | };
554 | 3C140DD82A51953200A8783E /* PBXTargetDependency */ = {
555 | isa = PBXTargetDependency;
556 | target = 3C140DBA2A51952F00A8783E /* demo */;
557 | targetProxy = 3C140DD72A51953200A8783E /* PBXContainerItemProxy */;
558 | };
559 | 3C140E3B2A51C81900A8783E /* PBXTargetDependency */ = {
560 | isa = PBXTargetDependency;
561 | target = 3C140E292A51C81600A8783E /* watchOS-demo Watch App */;
562 | targetProxy = 3C140E3A2A51C81900A8783E /* PBXContainerItemProxy */;
563 | };
564 | 3C140E452A51C81A00A8783E /* PBXTargetDependency */ = {
565 | isa = PBXTargetDependency;
566 | target = 3C140E292A51C81600A8783E /* watchOS-demo Watch App */;
567 | targetProxy = 3C140E442A51C81A00A8783E /* PBXContainerItemProxy */;
568 | };
569 | 3C140E4C2A51C81A00A8783E /* PBXTargetDependency */ = {
570 | isa = PBXTargetDependency;
571 | platformFilter = ios;
572 | target = 3C140E292A51C81600A8783E /* watchOS-demo Watch App */;
573 | targetProxy = 3C140E4B2A51C81A00A8783E /* PBXContainerItemProxy */;
574 | };
575 | /* End PBXTargetDependency section */
576 |
577 | /* Begin XCBuildConfiguration section */
578 | 3C140DDE2A51953200A8783E /* Debug */ = {
579 | isa = XCBuildConfiguration;
580 | buildSettings = {
581 | ALWAYS_SEARCH_USER_PATHS = NO;
582 | CLANG_ANALYZER_NONNULL = YES;
583 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
584 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
585 | CLANG_ENABLE_MODULES = YES;
586 | CLANG_ENABLE_OBJC_ARC = YES;
587 | CLANG_ENABLE_OBJC_WEAK = YES;
588 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
589 | CLANG_WARN_BOOL_CONVERSION = YES;
590 | CLANG_WARN_COMMA = YES;
591 | CLANG_WARN_CONSTANT_CONVERSION = YES;
592 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
593 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
594 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
595 | CLANG_WARN_EMPTY_BODY = YES;
596 | CLANG_WARN_ENUM_CONVERSION = YES;
597 | CLANG_WARN_INFINITE_RECURSION = YES;
598 | CLANG_WARN_INT_CONVERSION = YES;
599 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
600 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
601 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
602 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
603 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
604 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
605 | CLANG_WARN_STRICT_PROTOTYPES = YES;
606 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
607 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
608 | CLANG_WARN_UNREACHABLE_CODE = YES;
609 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
610 | COPY_PHASE_STRIP = NO;
611 | DEBUG_INFORMATION_FORMAT = dwarf;
612 | ENABLE_STRICT_OBJC_MSGSEND = YES;
613 | ENABLE_TESTABILITY = YES;
614 | GCC_C_LANGUAGE_STANDARD = gnu11;
615 | GCC_DYNAMIC_NO_PIC = NO;
616 | GCC_NO_COMMON_BLOCKS = YES;
617 | GCC_OPTIMIZATION_LEVEL = 0;
618 | GCC_PREPROCESSOR_DEFINITIONS = (
619 | "DEBUG=1",
620 | "$(inherited)",
621 | );
622 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
623 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
624 | GCC_WARN_UNDECLARED_SELECTOR = YES;
625 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
626 | GCC_WARN_UNUSED_FUNCTION = YES;
627 | GCC_WARN_UNUSED_VARIABLE = YES;
628 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
629 | MTL_FAST_MATH = YES;
630 | ONLY_ACTIVE_ARCH = YES;
631 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
632 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
633 | };
634 | name = Debug;
635 | };
636 | 3C140DDF2A51953200A8783E /* Release */ = {
637 | isa = XCBuildConfiguration;
638 | buildSettings = {
639 | ALWAYS_SEARCH_USER_PATHS = NO;
640 | CLANG_ANALYZER_NONNULL = YES;
641 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
642 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
643 | CLANG_ENABLE_MODULES = YES;
644 | CLANG_ENABLE_OBJC_ARC = YES;
645 | CLANG_ENABLE_OBJC_WEAK = YES;
646 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
647 | CLANG_WARN_BOOL_CONVERSION = YES;
648 | CLANG_WARN_COMMA = YES;
649 | CLANG_WARN_CONSTANT_CONVERSION = YES;
650 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
651 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
652 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
653 | CLANG_WARN_EMPTY_BODY = YES;
654 | CLANG_WARN_ENUM_CONVERSION = YES;
655 | CLANG_WARN_INFINITE_RECURSION = YES;
656 | CLANG_WARN_INT_CONVERSION = YES;
657 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
658 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
659 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
660 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
661 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
662 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
663 | CLANG_WARN_STRICT_PROTOTYPES = YES;
664 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
665 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
666 | CLANG_WARN_UNREACHABLE_CODE = YES;
667 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
668 | COPY_PHASE_STRIP = NO;
669 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
670 | ENABLE_NS_ASSERTIONS = NO;
671 | ENABLE_STRICT_OBJC_MSGSEND = YES;
672 | GCC_C_LANGUAGE_STANDARD = gnu11;
673 | GCC_NO_COMMON_BLOCKS = YES;
674 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
675 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
676 | GCC_WARN_UNDECLARED_SELECTOR = YES;
677 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
678 | GCC_WARN_UNUSED_FUNCTION = YES;
679 | GCC_WARN_UNUSED_VARIABLE = YES;
680 | MTL_ENABLE_DEBUG_INFO = NO;
681 | MTL_FAST_MATH = YES;
682 | SWIFT_COMPILATION_MODE = wholemodule;
683 | SWIFT_OPTIMIZATION_LEVEL = "-O";
684 | };
685 | name = Release;
686 | };
687 | 3C140DE12A51953200A8783E /* Debug */ = {
688 | isa = XCBuildConfiguration;
689 | buildSettings = {
690 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
691 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
692 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
693 | CODE_SIGN_ENTITLEMENTS = demo/demo.entitlements;
694 | CODE_SIGN_STYLE = Automatic;
695 | CURRENT_PROJECT_VERSION = 1;
696 | DEVELOPMENT_ASSET_PATHS = "\"demo/Preview Content\"";
697 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
698 | ENABLE_HARDENED_RUNTIME = YES;
699 | ENABLE_PREVIEWS = YES;
700 | GENERATE_INFOPLIST_FILE = YES;
701 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
702 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
703 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
704 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
705 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
706 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
707 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
708 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
709 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
710 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
711 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
712 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
713 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
714 | MACOSX_DEPLOYMENT_TARGET = 11.0;
715 | MARKETING_VERSION = 1.0;
716 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demo;
717 | PRODUCT_NAME = "$(TARGET_NAME)";
718 | SDKROOT = auto;
719 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
720 | SUPPORTS_MACCATALYST = YES;
721 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
722 | SWIFT_EMIT_LOC_STRINGS = YES;
723 | SWIFT_VERSION = 5.0;
724 | TARGETED_DEVICE_FAMILY = "1,2,3";
725 | TVOS_DEPLOYMENT_TARGET = 14.0;
726 | };
727 | name = Debug;
728 | };
729 | 3C140DE22A51953200A8783E /* Release */ = {
730 | isa = XCBuildConfiguration;
731 | buildSettings = {
732 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
733 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
734 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
735 | CODE_SIGN_ENTITLEMENTS = demo/demo.entitlements;
736 | CODE_SIGN_STYLE = Automatic;
737 | CURRENT_PROJECT_VERSION = 1;
738 | DEVELOPMENT_ASSET_PATHS = "\"demo/Preview Content\"";
739 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
740 | ENABLE_HARDENED_RUNTIME = YES;
741 | ENABLE_PREVIEWS = YES;
742 | GENERATE_INFOPLIST_FILE = YES;
743 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
744 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
745 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
746 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
747 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
748 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
749 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
750 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
751 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
752 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
753 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
754 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
755 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
756 | MACOSX_DEPLOYMENT_TARGET = 11.0;
757 | MARKETING_VERSION = 1.0;
758 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demo;
759 | PRODUCT_NAME = "$(TARGET_NAME)";
760 | SDKROOT = auto;
761 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
762 | SUPPORTS_MACCATALYST = YES;
763 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
764 | SWIFT_EMIT_LOC_STRINGS = YES;
765 | SWIFT_VERSION = 5.0;
766 | TARGETED_DEVICE_FAMILY = "1,2,3";
767 | TVOS_DEPLOYMENT_TARGET = 14.0;
768 | };
769 | name = Release;
770 | };
771 | 3C140DE42A51953200A8783E /* Debug */ = {
772 | isa = XCBuildConfiguration;
773 | buildSettings = {
774 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
775 | BUNDLE_LOADER = "$(TEST_HOST)";
776 | CODE_SIGN_STYLE = Automatic;
777 | CURRENT_PROJECT_VERSION = 1;
778 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
779 | GENERATE_INFOPLIST_FILE = YES;
780 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
781 | MACOSX_DEPLOYMENT_TARGET = 13.3;
782 | MARKETING_VERSION = 1.0;
783 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demoTests;
784 | PRODUCT_NAME = "$(TARGET_NAME)";
785 | SDKROOT = auto;
786 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
787 | SWIFT_EMIT_LOC_STRINGS = NO;
788 | SWIFT_VERSION = 5.0;
789 | TARGETED_DEVICE_FAMILY = "1,2";
790 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/demo";
791 | };
792 | name = Debug;
793 | };
794 | 3C140DE52A51953200A8783E /* Release */ = {
795 | isa = XCBuildConfiguration;
796 | buildSettings = {
797 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
798 | BUNDLE_LOADER = "$(TEST_HOST)";
799 | CODE_SIGN_STYLE = Automatic;
800 | CURRENT_PROJECT_VERSION = 1;
801 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
802 | GENERATE_INFOPLIST_FILE = YES;
803 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
804 | MACOSX_DEPLOYMENT_TARGET = 13.3;
805 | MARKETING_VERSION = 1.0;
806 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demoTests;
807 | PRODUCT_NAME = "$(TARGET_NAME)";
808 | SDKROOT = auto;
809 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
810 | SWIFT_EMIT_LOC_STRINGS = NO;
811 | SWIFT_VERSION = 5.0;
812 | TARGETED_DEVICE_FAMILY = "1,2";
813 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/demo";
814 | };
815 | name = Release;
816 | };
817 | 3C140DE72A51953200A8783E /* Debug */ = {
818 | isa = XCBuildConfiguration;
819 | buildSettings = {
820 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
821 | CODE_SIGN_STYLE = Automatic;
822 | CURRENT_PROJECT_VERSION = 1;
823 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
824 | GENERATE_INFOPLIST_FILE = YES;
825 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
826 | MACOSX_DEPLOYMENT_TARGET = 13.3;
827 | MARKETING_VERSION = 1.0;
828 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demoUITests;
829 | PRODUCT_NAME = "$(TARGET_NAME)";
830 | SDKROOT = auto;
831 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
832 | SWIFT_EMIT_LOC_STRINGS = NO;
833 | SWIFT_VERSION = 5.0;
834 | TARGETED_DEVICE_FAMILY = "1,2";
835 | TEST_TARGET_NAME = demo;
836 | };
837 | name = Debug;
838 | };
839 | 3C140DE82A51953200A8783E /* Release */ = {
840 | isa = XCBuildConfiguration;
841 | buildSettings = {
842 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
843 | CODE_SIGN_STYLE = Automatic;
844 | CURRENT_PROJECT_VERSION = 1;
845 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
846 | GENERATE_INFOPLIST_FILE = YES;
847 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
848 | MACOSX_DEPLOYMENT_TARGET = 13.3;
849 | MARKETING_VERSION = 1.0;
850 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demoUITests;
851 | PRODUCT_NAME = "$(TARGET_NAME)";
852 | SDKROOT = auto;
853 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
854 | SWIFT_EMIT_LOC_STRINGS = NO;
855 | SWIFT_VERSION = 5.0;
856 | TARGETED_DEVICE_FAMILY = "1,2";
857 | TEST_TARGET_NAME = demo;
858 | };
859 | name = Release;
860 | };
861 | 3C140E4F2A51C81B00A8783E /* Debug */ = {
862 | isa = XCBuildConfiguration;
863 | buildSettings = {
864 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
865 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
866 | CODE_SIGN_STYLE = Automatic;
867 | CURRENT_PROJECT_VERSION = 1;
868 | DEVELOPMENT_ASSET_PATHS = "\"watchOS-demo Watch App/Preview Content\"";
869 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
870 | ENABLE_PREVIEWS = YES;
871 | GENERATE_INFOPLIST_FILE = YES;
872 | INFOPLIST_KEY_CFBundleDisplayName = "watchOS-demo";
873 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
874 | INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.iankoex.blurhash.demo.demo;
875 | INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
876 | LD_RUNPATH_SEARCH_PATHS = (
877 | "$(inherited)",
878 | "@executable_path/Frameworks",
879 | );
880 | MARKETING_VERSION = 1.0;
881 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demo.watchkitapp;
882 | PRODUCT_NAME = "$(TARGET_NAME)";
883 | SDKROOT = watchos;
884 | SKIP_INSTALL = YES;
885 | SWIFT_EMIT_LOC_STRINGS = YES;
886 | SWIFT_VERSION = 5.0;
887 | TARGETED_DEVICE_FAMILY = 4;
888 | WATCHOS_DEPLOYMENT_TARGET = 7.0;
889 | };
890 | name = Debug;
891 | };
892 | 3C140E502A51C81B00A8783E /* Release */ = {
893 | isa = XCBuildConfiguration;
894 | buildSettings = {
895 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
896 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
897 | CODE_SIGN_STYLE = Automatic;
898 | CURRENT_PROJECT_VERSION = 1;
899 | DEVELOPMENT_ASSET_PATHS = "\"watchOS-demo Watch App/Preview Content\"";
900 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
901 | ENABLE_PREVIEWS = YES;
902 | GENERATE_INFOPLIST_FILE = YES;
903 | INFOPLIST_KEY_CFBundleDisplayName = "watchOS-demo";
904 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
905 | INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.iankoex.blurhash.demo.demo;
906 | INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES;
907 | LD_RUNPATH_SEARCH_PATHS = (
908 | "$(inherited)",
909 | "@executable_path/Frameworks",
910 | );
911 | MARKETING_VERSION = 1.0;
912 | PRODUCT_BUNDLE_IDENTIFIER = com.iankoex.blurhash.demo.demo.watchkitapp;
913 | PRODUCT_NAME = "$(TARGET_NAME)";
914 | SDKROOT = watchos;
915 | SKIP_INSTALL = YES;
916 | SWIFT_EMIT_LOC_STRINGS = YES;
917 | SWIFT_VERSION = 5.0;
918 | TARGETED_DEVICE_FAMILY = 4;
919 | VALIDATE_PRODUCT = YES;
920 | WATCHOS_DEPLOYMENT_TARGET = 7.0;
921 | };
922 | name = Release;
923 | };
924 | 3C140E532A51C81B00A8783E /* Debug */ = {
925 | isa = XCBuildConfiguration;
926 | buildSettings = {
927 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
928 | BUNDLE_LOADER = "$(TEST_HOST)";
929 | CODE_SIGN_STYLE = Automatic;
930 | CURRENT_PROJECT_VERSION = 1;
931 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
932 | GENERATE_INFOPLIST_FILE = YES;
933 | MARKETING_VERSION = 1.0;
934 | PRODUCT_BUNDLE_IDENTIFIER = "com.iankoex.blurhash.demo.watchOS-demo-Watch-AppTests";
935 | PRODUCT_NAME = "$(TARGET_NAME)";
936 | SDKROOT = watchos;
937 | SWIFT_EMIT_LOC_STRINGS = NO;
938 | SWIFT_VERSION = 5.0;
939 | TARGETED_DEVICE_FAMILY = 4;
940 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-demo Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOS-demo Watch App";
941 | WATCHOS_DEPLOYMENT_TARGET = 9.4;
942 | };
943 | name = Debug;
944 | };
945 | 3C140E542A51C81B00A8783E /* Release */ = {
946 | isa = XCBuildConfiguration;
947 | buildSettings = {
948 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
949 | BUNDLE_LOADER = "$(TEST_HOST)";
950 | CODE_SIGN_STYLE = Automatic;
951 | CURRENT_PROJECT_VERSION = 1;
952 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
953 | GENERATE_INFOPLIST_FILE = YES;
954 | MARKETING_VERSION = 1.0;
955 | PRODUCT_BUNDLE_IDENTIFIER = "com.iankoex.blurhash.demo.watchOS-demo-Watch-AppTests";
956 | PRODUCT_NAME = "$(TARGET_NAME)";
957 | SDKROOT = watchos;
958 | SWIFT_EMIT_LOC_STRINGS = NO;
959 | SWIFT_VERSION = 5.0;
960 | TARGETED_DEVICE_FAMILY = 4;
961 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/watchOS-demo Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/watchOS-demo Watch App";
962 | VALIDATE_PRODUCT = YES;
963 | WATCHOS_DEPLOYMENT_TARGET = 9.4;
964 | };
965 | name = Release;
966 | };
967 | 3C140E562A51C81B00A8783E /* Debug */ = {
968 | isa = XCBuildConfiguration;
969 | buildSettings = {
970 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
971 | CODE_SIGN_STYLE = Automatic;
972 | CURRENT_PROJECT_VERSION = 1;
973 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
974 | GENERATE_INFOPLIST_FILE = YES;
975 | MARKETING_VERSION = 1.0;
976 | PRODUCT_BUNDLE_IDENTIFIER = "com.iankoex.blurhash.demo.watchOS-demo-Watch-AppUITests";
977 | PRODUCT_NAME = "$(TARGET_NAME)";
978 | SDKROOT = watchos;
979 | SWIFT_EMIT_LOC_STRINGS = NO;
980 | SWIFT_VERSION = 5.0;
981 | TARGETED_DEVICE_FAMILY = 4;
982 | TEST_TARGET_NAME = "watchOS-demo Watch App";
983 | WATCHOS_DEPLOYMENT_TARGET = 9.4;
984 | };
985 | name = Debug;
986 | };
987 | 3C140E572A51C81B00A8783E /* Release */ = {
988 | isa = XCBuildConfiguration;
989 | buildSettings = {
990 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
991 | CODE_SIGN_STYLE = Automatic;
992 | CURRENT_PROJECT_VERSION = 1;
993 | DEVELOPMENT_TEAM = X7ZCGTWN8T;
994 | GENERATE_INFOPLIST_FILE = YES;
995 | MARKETING_VERSION = 1.0;
996 | PRODUCT_BUNDLE_IDENTIFIER = "com.iankoex.blurhash.demo.watchOS-demo-Watch-AppUITests";
997 | PRODUCT_NAME = "$(TARGET_NAME)";
998 | SDKROOT = watchos;
999 | SWIFT_EMIT_LOC_STRINGS = NO;
1000 | SWIFT_VERSION = 5.0;
1001 | TARGETED_DEVICE_FAMILY = 4;
1002 | TEST_TARGET_NAME = "watchOS-demo Watch App";
1003 | VALIDATE_PRODUCT = YES;
1004 | WATCHOS_DEPLOYMENT_TARGET = 9.4;
1005 | };
1006 | name = Release;
1007 | };
1008 | /* End XCBuildConfiguration section */
1009 |
1010 | /* Begin XCConfigurationList section */
1011 | 3C140DB62A51952F00A8783E /* Build configuration list for PBXProject "demo" */ = {
1012 | isa = XCConfigurationList;
1013 | buildConfigurations = (
1014 | 3C140DDE2A51953200A8783E /* Debug */,
1015 | 3C140DDF2A51953200A8783E /* Release */,
1016 | );
1017 | defaultConfigurationIsVisible = 0;
1018 | defaultConfigurationName = Release;
1019 | };
1020 | 3C140DE02A51953200A8783E /* Build configuration list for PBXNativeTarget "demo" */ = {
1021 | isa = XCConfigurationList;
1022 | buildConfigurations = (
1023 | 3C140DE12A51953200A8783E /* Debug */,
1024 | 3C140DE22A51953200A8783E /* Release */,
1025 | );
1026 | defaultConfigurationIsVisible = 0;
1027 | defaultConfigurationName = Release;
1028 | };
1029 | 3C140DE32A51953200A8783E /* Build configuration list for PBXNativeTarget "demoTests" */ = {
1030 | isa = XCConfigurationList;
1031 | buildConfigurations = (
1032 | 3C140DE42A51953200A8783E /* Debug */,
1033 | 3C140DE52A51953200A8783E /* Release */,
1034 | );
1035 | defaultConfigurationIsVisible = 0;
1036 | defaultConfigurationName = Release;
1037 | };
1038 | 3C140DE62A51953200A8783E /* Build configuration list for PBXNativeTarget "demoUITests" */ = {
1039 | isa = XCConfigurationList;
1040 | buildConfigurations = (
1041 | 3C140DE72A51953200A8783E /* Debug */,
1042 | 3C140DE82A51953200A8783E /* Release */,
1043 | );
1044 | defaultConfigurationIsVisible = 0;
1045 | defaultConfigurationName = Release;
1046 | };
1047 | 3C140E4E2A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch App" */ = {
1048 | isa = XCConfigurationList;
1049 | buildConfigurations = (
1050 | 3C140E4F2A51C81B00A8783E /* Debug */,
1051 | 3C140E502A51C81B00A8783E /* Release */,
1052 | );
1053 | defaultConfigurationIsVisible = 0;
1054 | defaultConfigurationName = Release;
1055 | };
1056 | 3C140E522A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch AppTests" */ = {
1057 | isa = XCConfigurationList;
1058 | buildConfigurations = (
1059 | 3C140E532A51C81B00A8783E /* Debug */,
1060 | 3C140E542A51C81B00A8783E /* Release */,
1061 | );
1062 | defaultConfigurationIsVisible = 0;
1063 | defaultConfigurationName = Release;
1064 | };
1065 | 3C140E552A51C81B00A8783E /* Build configuration list for PBXNativeTarget "watchOS-demo Watch AppUITests" */ = {
1066 | isa = XCConfigurationList;
1067 | buildConfigurations = (
1068 | 3C140E562A51C81B00A8783E /* Debug */,
1069 | 3C140E572A51C81B00A8783E /* Release */,
1070 | );
1071 | defaultConfigurationIsVisible = 0;
1072 | defaultConfigurationName = Release;
1073 | };
1074 | /* End XCConfigurationList section */
1075 |
1076 | /* Begin XCSwiftPackageProductDependency section */
1077 | 3C140DEC2A51960200A8783E /* UnifiedBlurHash */ = {
1078 | isa = XCSwiftPackageProductDependency;
1079 | productName = UnifiedBlurHash;
1080 | };
1081 | 3C140E5A2A51C8CC00A8783E /* UnifiedBlurHash */ = {
1082 | isa = XCSwiftPackageProductDependency;
1083 | productName = UnifiedBlurHash;
1084 | };
1085 | /* End XCSwiftPackageProductDependency section */
1086 | };
1087 | rootObject = 3C140DB32A51952F00A8783E /* Project object */;
1088 | }
1089 |
--------------------------------------------------------------------------------
/demo/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/demo/demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demo/demo.xcodeproj/xcshareddata/xcschemes/demo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
36 |
42 |
43 |
44 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
69 |
75 |
76 |
77 |
78 |
84 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/demo/demo.xcodeproj/xcshareddata/xcschemes/watchOS-demo Watch App.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
46 |
47 |
50 |
56 |
57 |
58 |
61 |
67 |
68 |
69 |
70 |
71 |
81 |
83 |
89 |
90 |
91 |
92 |
98 |
100 |
106 |
107 |
108 |
109 |
111 |
112 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/demo/demo/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demo/demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/demo/demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo/demo/Assets.xcassets/sunflower.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "sunflower.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/demo/Assets.xcassets/sunflower.imageset/sunflower.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iankoex/UnifiedBlurHash/675d79835d6338e3fd4a314ef3220ae41f7eb6d4/demo/demo/Assets.xcassets/sunflower.imageset/sunflower.jpg
--------------------------------------------------------------------------------
/demo/demo/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // demo
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import SwiftUI
9 | import UnifiedBlurHash
10 |
11 | struct ContentView: View {
12 | @State private var imageString = "LWC$.Wx]Sjof~Wx]X9oe?btRofax"
13 | @State private var imageDecodedFromBlurHashString: UnifiedImage? = nil
14 | @State private var averageColorFromBlurHashString: Color?
15 |
16 | var body: some View {
17 | List {
18 | Section(content: {
19 | Button("Encode", action: encode)
20 |
21 | Button("Decode", action: decode)
22 | })
23 |
24 | Section(content: {
25 | Image("sunflower")
26 | .resizable()
27 | .aspectRatio(9/16, contentMode: .fit)
28 | .frame(maxWidth: 300)
29 | }, header: {
30 | Text("Original Image")
31 | })
32 |
33 | Section(content: {
34 | if let imageDecodedFromBlurHashString {
35 | Image(unifiedImage: imageDecodedFromBlurHashString)
36 | .resizable()
37 | .aspectRatio(9/16, contentMode: .fit)
38 | .frame(maxWidth: 300)
39 | }
40 | }, header: {
41 | Text("Image Decoded From BlurHash String")
42 | })
43 |
44 | Section(content: {
45 | averageColorFromBlurHashString?.frame(width: 300, height: 300)
46 | }, header: {
47 | Text("Average Color From BlurHash String")
48 | })
49 | }
50 | }
51 |
52 | private func encode() {
53 | Task {
54 | // https://www.pexels.com/photo/person-holding-yellow-sunflower-1624076/
55 | let image = UnifiedImage(named: "sunflower")
56 | guard let image = image else {
57 | return
58 | }
59 | let str = await UnifiedBlurHash.getBlurHashString(from: image)
60 | guard let str = str else {
61 | return
62 | }
63 | DispatchQueue.main.async {
64 | self.imageString = str
65 | }
66 | }
67 | }
68 |
69 | private func decode() {
70 | Task {
71 | let image = await UnifiedBlurHash.getUnifiedImage(from: imageString)
72 | self.averageColorFromBlurHashString = await UnifiedBlurHash.getAverageColor(from: imageString)
73 | guard let image = image else {
74 | return
75 | }
76 | DispatchQueue.main.async {
77 | self.imageDecodedFromBlurHashString = image
78 | }
79 | }
80 | }
81 | }
82 |
83 | struct ContentView_Previews: PreviewProvider {
84 | static var previews: some View {
85 | ContentView()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/demo/demo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo/demo/demo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/demo/demo/demoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // demoApp.swift
3 | // demo
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct demoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/demoTests/demoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // demoTests.swift
3 | // demoTests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 |
10 | final class demoTests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | // This is an example of a functional test case.
22 | // Use XCTAssert and related functions to verify your tests produce the correct results.
23 | // Any test you write for XCTest can be annotated as throws and async.
24 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
25 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
26 | }
27 |
28 | func testPerformanceExample() throws {
29 | // This is an example of a performance test case.
30 | measure {
31 | // Put the code you want to measure the time of here.
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/demo/demoUITests/demoUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // demoUITests.swift
3 | // demoUITests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 |
10 | final class demoUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testLaunchPerformance() throws {
34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
35 | // This measures how long it takes to launch your application.
36 | measure(metrics: [XCTApplicationLaunchMetric()]) {
37 | XCUIApplication().launch()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/demo/demoUITests/demoUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // demoUITestsLaunchTests.swift
3 | // demoUITests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 |
10 | final class demoUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "watchos",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Assets.xcassets/sunflower.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pexels-lil-artsy-1624076-2.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Assets.xcassets/sunflower.imageset/pexels-lil-artsy-1624076-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iankoex/UnifiedBlurHash/675d79835d6338e3fd4a314ef3220ae41f7eb6d4/demo/watchOS-demo Watch App/Assets.xcassets/sunflower.imageset/pexels-lil-artsy-1624076-2.jpg
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // demo
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import SwiftUI
9 | import UnifiedBlurHash
10 |
11 | struct ContentView: View {
12 | @State private var imageString = "LWC$.Wx]Sjof~Wx]X9oe?btRofax"
13 | @State private var image: UnifiedImage? = nil
14 |
15 | var body: some View {
16 | ScrollView {
17 | VStack(spacing: 20) {
18 | Button("Encode", action: encode)
19 |
20 | Button("Decode", action: decode)
21 |
22 | Image("sunflower")
23 | .resizable()
24 | .aspectRatio(9/16, contentMode: .fit)
25 | .frame(maxWidth: 300)
26 |
27 | if image != nil {
28 | Image(unifiedImage: image!)
29 | .resizable()
30 | .aspectRatio(9/16, contentMode: .fit)
31 | .frame(maxWidth: 300)
32 | }
33 | }
34 | .padding(.horizontal)
35 | }
36 | }
37 |
38 | private func encode() {
39 | Task {
40 | // https://www.pexels.com/photo/person-holding-yellow-sunflower-1624076/
41 | let image = UnifiedImage(named: "sunflower")
42 | guard let image = image else {
43 | return
44 | }
45 | let str = await UnifiedBlurHash.getBlurHashString(from: image)
46 | guard let str = str else {
47 | return
48 | }
49 | DispatchQueue.main.async {
50 | self.imageString = str
51 | }
52 | }
53 | }
54 |
55 | private func decode() {
56 | Task {
57 | let image = await UnifiedBlurHash.getUnifiedImage(from: imageString)
58 | guard let image = image else {
59 | return
60 | }
61 | DispatchQueue.main.async {
62 | self.image = image
63 | }
64 | }
65 | }
66 | }
67 |
68 | struct ContentView_Previews: PreviewProvider {
69 | static var previews: some View {
70 | ContentView()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch App/watchOS_demoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // watchOS_demoApp.swift
3 | // watchOS-demo Watch App
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct watchOS_demo_Watch_AppApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch AppTests/watchOS_demo_Watch_AppTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // watchOS_demo_Watch_AppTests.swift
3 | // watchOS-demo Watch AppTests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 | @testable import watchOS_demo_Watch_App
10 |
11 | final class watchOS_demo_Watch_AppTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Tests marked async will run the test method on an arbitrary thread managed by the Swift runtime.
27 | }
28 |
29 | func testPerformanceExample() throws {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch AppUITests/watchOS_demo_Watch_AppUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // watchOS_demo_Watch_AppUITests.swift
3 | // watchOS-demo Watch AppUITests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 |
10 | final class watchOS_demo_Watch_AppUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testLaunchPerformance() throws {
34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
35 | // This measures how long it takes to launch your application.
36 | measure(metrics: [XCTApplicationLaunchMetric()]) {
37 | XCUIApplication().launch()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/demo/watchOS-demo Watch AppUITests/watchOS_demo_Watch_AppUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // watchOS_demo_Watch_AppUITestsLaunchTests.swift
3 | // watchOS-demo Watch AppUITests
4 | //
5 | // Created by Ian on 02/07/2023.
6 | //
7 |
8 | import XCTest
9 |
10 | final class watchOS_demo_Watch_AppUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------