├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── Package.swift
├── Playgrounds
└── VisualEffects.playground
│ ├── Contents.swift
│ ├── contents.xcplayground
│ └── timeline.xctimeline
├── README.md
├── Sources
└── VisualEffects
│ └── Blur
│ ├── VisualEffectBlur.iOS.swift
│ └── VisualEffectBlur.macOS.swift
└── Tests
└── VisualEffectsTests
└── VisualEffectsTests.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [spacenation]
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Build
17 | run: swift build -v
18 | - name: Run tests
19 | run: swift test -v
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.xcodeproj
3 | xcuserdata/
4 |
5 | Packages/
6 | .build/
7 | .swiftpm
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 SpaceNation Inc.
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.3
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: "VisualEffects",
8 | platforms: [
9 | .iOS(.v13), .macOS(.v10_15)
10 | ],
11 | products: [
12 | .library(name: "VisualEffects", targets: ["VisualEffects"])
13 | ],
14 | targets: [
15 | .target(name: "VisualEffects", dependencies: []),
16 | .testTarget(name: "VisualEffectsTests", dependencies: ["VisualEffects"])
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/Playgrounds/VisualEffects.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import VisualEffects
3 | import PlaygroundSupport
4 |
5 | struct ContentView: View {
6 | var body: some View {
7 | ZStack {
8 | Rectangle()
9 | .foregroundColor(.blue)
10 | .frame(width: 100, height: 100)
11 | Rectangle()
12 | .opacity(0.5)
13 | .frame(width: 200, height: 200)
14 | .background(
15 | VisualEffectBlur()
16 | )
17 | }
18 | }
19 | }
20 |
21 | PlaygroundPage.current.setLiveView(ContentView())
22 |
--------------------------------------------------------------------------------
/Playgrounds/VisualEffects.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Playgrounds/VisualEffects.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## SwiftUI Visual Effects
2 |
3 | ### VisualEffectBlur
4 | ```swift
5 | .background(
6 | VisualEffectBlur()
7 | )
8 | ```
9 |
10 | ## Code Contributions
11 | Feel free to contribute via fork/pull request to master branch. If you want to request a feature or report a bug please start a new issue.
12 |
13 | ## Coffee Contributions
14 | If you find this project useful please consider becoming my GitHub sponsor.
15 |
--------------------------------------------------------------------------------
/Sources/VisualEffects/Blur/VisualEffectBlur.iOS.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS)
2 | import SwiftUI
3 |
4 | public struct VisualEffectBlur: View {
5 | var blurStyle: UIBlurEffect.Style
6 | var vibrancyStyle: UIVibrancyEffectStyle?
7 | var content: Content
8 |
9 | public init(blurStyle: UIBlurEffect.Style = .systemMaterial, vibrancyStyle: UIVibrancyEffectStyle? = nil, @ViewBuilder content: () -> Content) {
10 | self.blurStyle = blurStyle
11 | self.vibrancyStyle = vibrancyStyle
12 | self.content = content()
13 | }
14 |
15 | public var body: some View {
16 | Representable(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle, content: ZStack { content })
17 | .accessibility(hidden: Content.self == EmptyView.self)
18 | }
19 | }
20 |
21 | extension VisualEffectBlur {
22 | struct Representable: UIViewRepresentable {
23 | var blurStyle: UIBlurEffect.Style
24 | var vibrancyStyle: UIVibrancyEffectStyle?
25 | var content: Content
26 |
27 | func makeUIView(context: Context) -> UIVisualEffectView {
28 | context.coordinator.blurView
29 | }
30 |
31 | func updateUIView(_ view: UIVisualEffectView, context: Context) {
32 | context.coordinator.update(content: content, blurStyle: blurStyle, vibrancyStyle: vibrancyStyle)
33 | }
34 |
35 | func makeCoordinator() -> Coordinator {
36 | Coordinator(content: content)
37 | }
38 | }
39 | }
40 |
41 | // MARK: - Coordinator
42 |
43 | extension VisualEffectBlur.Representable {
44 | class Coordinator {
45 | let blurView = UIVisualEffectView()
46 | let vibrancyView = UIVisualEffectView()
47 | let hostingController: UIHostingController
48 |
49 | init(content: Content) {
50 | hostingController = UIHostingController(rootView: content)
51 | hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
52 | hostingController.view.backgroundColor = nil
53 | blurView.contentView.addSubview(vibrancyView)
54 | blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
55 | vibrancyView.contentView.addSubview(hostingController.view)
56 | vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
57 | }
58 |
59 | func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) {
60 | hostingController.rootView = content
61 | let blurEffect = UIBlurEffect(style: blurStyle)
62 | blurView.effect = blurEffect
63 | if let vibrancyStyle = vibrancyStyle {
64 | vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle)
65 | } else {
66 | vibrancyView.effect = nil
67 | }
68 | hostingController.view.setNeedsDisplay()
69 | }
70 | }
71 | }
72 |
73 | public extension VisualEffectBlur where Content == EmptyView {
74 | init(blurStyle: UIBlurEffect.Style = .systemMaterial, vibrancyStyle: UIVibrancyEffectStyle? = nil) {
75 | self.init(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle) {
76 | EmptyView()
77 | }
78 | }
79 | }
80 |
81 | #endif
82 |
--------------------------------------------------------------------------------
/Sources/VisualEffects/Blur/VisualEffectBlur.macOS.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import SwiftUI
3 |
4 | public struct VisualEffectBlur: View {
5 | private var material: NSVisualEffectView.Material
6 | private let blendingMode: NSVisualEffectView.BlendingMode
7 | private let isEmphasized: Bool
8 |
9 | public init(material: NSVisualEffectView.Material = .headerView, blendingMode: NSVisualEffectView.BlendingMode = .withinWindow, isEmphasized: Bool = false) {
10 | self.material = material
11 | self.blendingMode = blendingMode
12 | self.isEmphasized = isEmphasized
13 | }
14 |
15 | public var body: some View {
16 | Representable(material: material, blendingMode: blendingMode, isEmphasized: isEmphasized)
17 | .accessibility(hidden: true)
18 | }
19 | }
20 |
21 | extension VisualEffectBlur {
22 | struct Representable: NSViewRepresentable {
23 | var material: NSVisualEffectView.Material
24 | var blendingMode: NSVisualEffectView.BlendingMode
25 | var isEmphasized: Bool
26 |
27 | func makeNSView(context: Context) -> NSVisualEffectView {
28 | context.coordinator.visualEffectView
29 | }
30 |
31 | func updateNSView(_ view: NSVisualEffectView, context: Context) {
32 | context.coordinator.update(material: material, blendingMode: blendingMode, isEmphasized: isEmphasized)
33 | }
34 |
35 | func makeCoordinator() -> Coordinator {
36 | Coordinator()
37 | }
38 | }
39 |
40 | class Coordinator {
41 | let visualEffectView = NSVisualEffectView()
42 |
43 | func update(material: NSVisualEffectView.Material, blendingMode: NSVisualEffectView.BlendingMode, isEmphasized: Bool) {
44 | visualEffectView.material = material
45 | visualEffectView.blendingMode = blendingMode
46 | visualEffectView.isEmphasized = isEmphasized
47 | }
48 | }
49 | }
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/Tests/VisualEffectsTests/VisualEffectsTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import VisualEffects
3 |
4 | final class VisualEffectsTests: XCTestCase {
5 | func testEmpty() {
6 | }
7 |
8 | static var allTests = [
9 | ("testEmpty", testEmpty),
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------