├── .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 | --------------------------------------------------------------------------------