├── Resources ├── icon.png ├── IslandAlerts.png ├── NotchAlerts.png └── IslandAlertsForSwiftUI.png ├── Sources └── IslandAlertsForSwiftUI │ ├── IslandAlertsForSwiftUI.swift │ ├── NotchLargeAlert │ ├── NotchLargeAlert.gif │ ├── NotchLargeAlertExample.swift │ ├── README.md │ └── NotchLargeAlert.swift │ ├── IslandLargeAlert │ ├── IslandLargeAlert.gif │ ├── IslandLargeAlertExample.swift │ ├── README.md │ └── IslandLargeAlert.swift │ ├── NotchMediumAlert │ ├── NotchMediumAlert.gif │ ├── NotchMediumAlertExample.swift │ ├── README.md │ └── NotchMediumAlert.swift │ ├── IslandMediumAlert │ ├── IslandMediumAlert.gif │ ├── IslandMediumAlertExample.swift │ ├── README.md │ └── IslandMediumAlert.swift │ ├── IslandSquareAlert │ ├── IslandSquareAlert.gif │ ├── IslandSquareAlertExample.swift │ ├── README.md │ └── IslandSquareAlert.swift │ └── Utils │ ├── RoundedCorners.swift │ ├── IslandAnimation.swift │ ├── Notch.swift │ ├── DynamicIsland.swift │ └── ModelName.swift ├── Tests └── IslandAlertsForSwiftUITests │ └── IslandAlertsForSwiftUITests.swift ├── LICENSE ├── Package.swift └── README.md /Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Resources/icon.png -------------------------------------------------------------------------------- /Resources/IslandAlerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Resources/IslandAlerts.png -------------------------------------------------------------------------------- /Resources/NotchAlerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Resources/NotchAlerts.png -------------------------------------------------------------------------------- /Resources/IslandAlertsForSwiftUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Resources/IslandAlertsForSwiftUI.png -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandAlertsForSwiftUI.swift: -------------------------------------------------------------------------------- 1 | public struct IslandAlertsForSwiftUI { 2 | public private(set) var text = "Hello, World!" 3 | 4 | public init() { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchLargeAlert/NotchLargeAlert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Sources/IslandAlertsForSwiftUI/NotchLargeAlert/NotchLargeAlert.gif -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandLargeAlert/IslandLargeAlert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Sources/IslandAlertsForSwiftUI/IslandLargeAlert/IslandLargeAlert.gif -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchMediumAlert/NotchMediumAlert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Sources/IslandAlertsForSwiftUI/NotchMediumAlert/NotchMediumAlert.gif -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandMediumAlert/IslandMediumAlert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Sources/IslandAlertsForSwiftUI/IslandMediumAlert/IslandMediumAlert.gif -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandSquareAlert/IslandSquareAlert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessiorubicini/IslandAlertsForSwiftUI/HEAD/Sources/IslandAlertsForSwiftUI/IslandSquareAlert/IslandSquareAlert.gif -------------------------------------------------------------------------------- /Tests/IslandAlertsForSwiftUITests/IslandAlertsForSwiftUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import IslandAlertsForSwiftUI 3 | 4 | final class IslandAlertsForSwiftUITests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(IslandAlertsForSwiftUI().text, "Hello, World!") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/Utils/RoundedCorners.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedCorners.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 21/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension View { 12 | func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { 13 | clipShape( RoundedCorner(radius: radius, corners: corners) ) 14 | } 15 | } 16 | 17 | struct RoundedCorner: Shape { 18 | 19 | var radius: CGFloat = .infinity 20 | var corners: UIRectCorner = .allCorners 21 | 22 | func path(in rect: CGRect) -> Path { 23 | let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 24 | return Path(path.cgPath) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/Utils/IslandAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct IslandAnimation: ViewModifier { 12 | 13 | @Binding var isPresented: Bool 14 | 15 | public func body(content: Content) -> some View { 16 | content 17 | .scaleEffect(isPresented ? 1 : 0, anchor: .topLeading) 18 | .blur(radius: isPresented ? 0: 10) 19 | } 20 | } 21 | 22 | extension View { 23 | // Pop and blur animation for Dynamic Island shrinking 24 | public func islandAnimation(isPresented: Binding) -> some View { 25 | ModifiedContent(content: self, modifier: IslandAnimation(isPresented: isPresented)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchMediumAlert/NotchMediumAlertExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchMediumAlertExample.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 21/09/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct NotchMediumAlertExample: View { 11 | 12 | @State private var alert = false 13 | 14 | public var body: some View { 15 | Button(UIDevice.modelName) { 16 | withAnimation { 17 | alert.toggle() 18 | } 19 | } 20 | .notchMediumAlert(isPresented: $alert, systemIcon: "airpodspro", title: "Airpods connected") 21 | } 22 | } 23 | 24 | struct NotchMediumAlertExample_Previews: PreviewProvider { 25 | static var previews: some View { 26 | NotchMediumAlertExample() 27 | .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro")) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandSquareAlert/IslandSquareAlertExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandSquareAlertExample.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct IslandSquareAlertExample: View { 11 | 12 | @State private var alert = false 13 | 14 | public var body: some View { 15 | Button("Toggle alert") { 16 | withAnimation { 17 | alert.toggle() 18 | } 19 | } 20 | .buttonStyle(.borderedProminent) 21 | 22 | .islandSquareAlert(isPresented: $alert, systemIcon: "faceid", text: "Login") 23 | } 24 | } 25 | 26 | struct IslandSquareAlertExample_Previews: PreviewProvider { 27 | static var previews: some View { 28 | IslandSquareAlertExample() 29 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandMediumAlert/IslandMediumAlertExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandMediumAlertExample.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct IslandMediumAlertExample: View { 11 | 12 | @State private var alert = false 13 | 14 | public var body: some View { 15 | Button("Toggle alert") { 16 | withAnimation { 17 | alert.toggle() 18 | } 19 | } 20 | .buttonStyle(.borderedProminent) 21 | 22 | .islandMediumAlert(isPresented: $alert, systemIcon: "airpodspro", title: "Airpods connected") 23 | } 24 | } 25 | 26 | struct IslandMediumAlertExample_Previews: PreviewProvider { 27 | static var previews: some View { 28 | IslandMediumAlertExample() 29 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchLargeAlert/NotchLargeAlertExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchLargeAlertExample.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct NotchLargeAlertExample: View { 11 | 12 | @State private var alert = false 13 | 14 | public var body: some View { 15 | Button("Toggle alert") { 16 | withAnimation { 17 | alert.toggle() 18 | } 19 | } 20 | .notchLargeAlert(isPresented: $alert, title: "Hello, Island", message: "This is a test for the new Notch alert.", action: { 21 | alert.toggle() 22 | }) 23 | } 24 | } 25 | 26 | struct NotchLargeAlertExample_Previews: PreviewProvider { 27 | static var previews: some View { 28 | NotchLargeAlertExample() 29 | .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro")) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandLargeAlert/IslandLargeAlertExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandLargeAlertExample.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct IslandLargeAlertExample: View { 11 | 12 | @State private var alert = false 13 | 14 | public var body: some View { 15 | Button("Toggle alert") { 16 | withAnimation { 17 | alert.toggle() 18 | } 19 | } 20 | .buttonStyle(.borderedProminent) 21 | 22 | .islandLargeAlert(isPresented: $alert, title: "Hi! Alert!", message: "This is a test for presenting an alert from the Dynamic Island.", action: { 23 | alert.toggle() 24 | }) 25 | } 26 | } 27 | 28 | struct IslandLargeAlertExample_Previews: PreviewProvider { 29 | static var previews: some View { 30 | IslandLargeAlertExample() 31 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Alessio Rubicini 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandSquareAlert/README.md: -------------------------------------------------------------------------------- 1 | # Island Square Alert 2 | 3 | > A small-size alert expanding into a square from the Dynamic Island. It's dismissed by tapping on it. 4 | 5 | **How can I dismiss it through UI?** By tapping on it. 6 | 7 | ## Parameters 8 | 9 | - **isPresented**: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 10 | - **systemIcon**: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 11 | - **text**: A text string used as the text of the alert. 12 | 13 | ## Code 14 | 15 | ```swift 16 | struct Example: View { 17 | 18 | @State private var showAlert = false 19 | 20 | public var body: some View { 21 | Button("Toggle alert") { 22 | withAnimation { 23 | showAlert.toggle() 24 | } 25 | } 26 | 27 | .islandSquareAlert(isPresented: $showAlert, systemIcon: "faceid", text: "Login") 28 | } 29 | } 30 | 31 | ``` 32 | 33 | ## Result 34 | 35 | ![Result](IslandSquareAlert.gif) 36 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandMediumAlert/README.md: -------------------------------------------------------------------------------- 1 | # Island Medium Alert 2 | 3 | > A medium-size alert expanding into a top rectangle from the Dynamic Island 4 | 5 | **How can I dismiss it through UI?** By tapping on the right xmark. 6 | 7 | ## Parameters 8 | 9 | - **isPresented**: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 10 | - **systemIcon**: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 11 | - **text**: A text string used as the text of the alert. 12 | 13 | ## Code 14 | 15 | ```swift 16 | struct Example: View { 17 | 18 | @State private var showAlert = false 19 | 20 | public var body: some View { 21 | Button("Toggle alert") { 22 | withAnimation { 23 | showAlert.toggle() 24 | } 25 | } 26 | 27 | .islandMediumAlert(isPresented: $showAlert, systemIcon: "airpodspro", title: "Airpods connected") 28 | } 29 | } 30 | 31 | ``` 32 | 33 | ## Result 34 | 35 | ![Result](IslandMediumAlert.gif) 36 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandLargeAlert/README.md: -------------------------------------------------------------------------------- 1 | # Island Large Alert 2 | 3 | > A large alert expanding from Dynamic Island with cancel and confirmation buttons 4 | 5 | ## Parameters 6 | 7 | - **isPresented**: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 8 | - **title**: A text string used as the title of the alert. 9 | - **message**: A text string used as the message of the alert, maximum 3 lines of text after which it is truncated 10 | - **action**: function performed when the Confirm button is pressed 11 | 12 | ## Code 13 | 14 | ```swift 15 | struct Example: View { 16 | 17 | @State private var showAlert = false 18 | 19 | public var body: some View { 20 | Button("Toggle alert") { 21 | withAnimation { 22 | showAlert.toggle() 23 | } 24 | } 25 | 26 | .islandLargeAlert(isPresented: $showAlert, title: "Hello, Island,", message: "This is a test for presenting an alert from the Dynamic Island.", action: { 27 | ... 28 | }) 29 | } 30 | } 31 | 32 | ``` 33 | 34 | ## Result 35 | 36 | ![Result](IslandLargeAlert.gif) 37 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchMediumAlert/README.md: -------------------------------------------------------------------------------- 1 | # Notch Medium Alert 2 | 3 | > A medium-size alert expanding into a top rectangle from the notch. 4 | 5 | Compatible both with larger notch (iPhone 12 or older) and reduced notch (iPhone 13 or older). 6 | 7 | **How can I dismiss it through UI?** By tapping on it. 8 | 9 | ## Parameters 10 | 11 | - **isPresented**: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 12 | - **systemIcon**: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 13 | - **text**: A text string used as the text of the alert. 14 | 15 | ## Code 16 | 17 | ```swift 18 | struct Example: View { 19 | 20 | @State private var showAlert = false 21 | 22 | public var body: some View { 23 | Button("Toggle alert") { 24 | withAnimation { 25 | showAlert.toggle() 26 | } 27 | } 28 | 29 | .notchMediumAlert(isPresented: $alert, systemIcon: "airpodspro", title: "Airpods connected") 30 | 31 | } 32 | } 33 | 34 | ``` 35 | 36 | ## Result 37 | 38 | ![Result](NotchMediumAlert.gif) 39 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 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: "IslandAlertsForSwiftUI", 8 | defaultLocalization: "en", 9 | platforms: [.iOS(.v15)], 10 | products: [ 11 | // Products define the executables and libraries a package produces, and make them visible to other packages. 12 | .library( 13 | name: "IslandAlertsForSwiftUI", 14 | targets: ["IslandAlertsForSwiftUI"]), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | // .package(url: /* package url */, from: "1.0.0"), 19 | ], 20 | targets: [ 21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 22 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 23 | .target( 24 | name: "IslandAlertsForSwiftUI", 25 | dependencies: []), 26 | .testTarget( 27 | name: "IslandAlertsForSwiftUITests", 28 | dependencies: ["IslandAlertsForSwiftUI"]), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchLargeAlert/README.md: -------------------------------------------------------------------------------- 1 | # Notch Large Alert 2 | 3 | > A large-size alert expanding completely from the top notch with cancel and confirmation buttons 4 | 5 | Compatible both with larger notch (iPhone 12 or older) and reduced notch (iPhone 13 or older). 6 | 7 | ## Parameters 8 | 9 | - **isPresented**: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 10 | - **title**: A text string used as the title of the alert. 11 | - **message**: A text string used as the message of the alert, maximum 3 lines of text after which it is truncated 12 | - **action**: function performed when the Confirm button is pressed 13 | 14 | ## Code 15 | 16 | ```swift 17 | struct Example: View { 18 | 19 | @State private var showAlert = false 20 | 21 | public var body: some View { 22 | Button("Toggle alert") { 23 | withAnimation { 24 | showAlert.toggle() 25 | } 26 | } 27 | 28 | .notchLargeAlert(isPresented: $alert, title: "Hello, Island", message: "This is a test for the new Notch alert.", action: { 29 | alert.toggle() 30 | }) 31 | } 32 | } 33 | 34 | ``` 35 | 36 | ## Result 37 | 38 | ![Result](NotchLargeAlert.gif) 39 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/Utils/Notch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notch.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | private class Notch { 12 | 13 | private init() {} 14 | 15 | enum NotchIphoneModels { 16 | case iPhoneX, iPhoneXs, iPhoneXsMax, iPhoneXr, iPhone11, iPhone11Pro, iPhone11ProMax, iPhone12Mini, iPhone12, iPhone12Pro, iPhone12ProMax, iPhone13, iPhone13Pro, iPhone13ProMax, iPhone13Mini, iPhone14, iPhone14Plus 17 | } 18 | 19 | static var width: CGFloat { 20 | let bigNotchModels = ["X", "11", "12"] 21 | if UIDevice.modelName == "iPhone 11" { 22 | return 230 23 | } else if bigNotchModels.first(where: {UIDevice.modelName.contains($0)}) != nil { 24 | return 210 25 | } else { 26 | return 160 27 | } 28 | } 29 | 30 | struct NotchLargeFrame: ViewModifier { 31 | @Binding var isPresented: Bool 32 | func body(content: Content) -> some View { 33 | content 34 | .frame(width: isPresented ? UIScreen.main.bounds.width : Notch.width-3, height: isPresented ? 210:30) 35 | } 36 | } 37 | 38 | struct NotchMediumFrame: ViewModifier { 39 | @Binding var isPresented: Bool 40 | func body(content: Content) -> some View { 41 | content 42 | .frame(width: isPresented ? Notch.width : Notch.width-2, height: isPresented ? 150:28) 43 | } 44 | } 45 | } 46 | 47 | extension View { 48 | func notchLargeFrame(isPresented: Binding) -> some View { 49 | ModifiedContent(content: self, modifier: Notch.NotchLargeFrame(isPresented: isPresented)) 50 | } 51 | func notchMediumFrame(isPresented: Binding) -> some View { 52 | ModifiedContent(content: self, modifier: Notch.NotchMediumFrame(isPresented: isPresented)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/Utils/DynamicIsland.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicIsland.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | private class DynamicIsland { 12 | 13 | static let width: CGFloat = 125 14 | static let heigth: CGFloat = 35 15 | 16 | private init() {} 17 | 18 | static var islandExpandedSize: CGFloat { 19 | UIDevice.modelName.contains("Pro Max") ? 400 : 360 20 | } 21 | 22 | struct IslandLargeFrame: ViewModifier { 23 | @Binding var isPresented: Bool 24 | func body(content: Content) -> some View { 25 | content 26 | .frame(width: isPresented ? DynamicIsland.islandExpandedSize:DynamicIsland.width, height: isPresented ? 200:DynamicIsland.heigth) 27 | } 28 | } 29 | 30 | struct IslandMediumFrame: ViewModifier { 31 | @Binding var isPresented: Bool 32 | func body(content: Content) -> some View { 33 | content 34 | .frame(width: isPresented ? DynamicIsland.islandExpandedSize:DynamicIsland.width, height: isPresented ? 90:DynamicIsland.heigth) 35 | } 36 | } 37 | 38 | struct IslandSquareFrame: ViewModifier { 39 | @Binding var isPresented: Bool 40 | func body(content: Content) -> some View { 41 | content 42 | .frame(width: isPresented ? 140:DynamicIsland.width, height: isPresented ? 140:DynamicIsland.heigth) 43 | } 44 | } 45 | } 46 | 47 | extension View { 48 | func islandLargeFrame(isPresented: Binding) -> some View { 49 | ModifiedContent(content: self, modifier: DynamicIsland.IslandLargeFrame(isPresented: isPresented)) 50 | } 51 | func islandMediumFrame(isPresented: Binding) -> some View { 52 | ModifiedContent(content: self, modifier: DynamicIsland.IslandMediumFrame(isPresented: isPresented)) 53 | } 54 | func islandSquareFrame(isPresented: Binding) -> some View { 55 | ModifiedContent(content: self, modifier: DynamicIsland.IslandSquareFrame(isPresented: isPresented)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Package Logo 3 |

Island Alerts for SwiftUI

4 |

5 | IslandAlertsForSwiftUI provides a variety of SwiftUI alerts that integrate perfectly with iPhone’s Dynamic Island and Notch! 6 |
7 |

8 |
9 | 10 | 22 | 23 | ## Types of alert 24 | 25 | ### Dynamic Island 26 | 27 | ![IslandAlerts](Resources/IslandAlerts.png) 28 | 29 | - **[IslandLargeAlert](Sources/IslandAlertsForSwiftUI/IslandLargeAlert/README.md)**: a large-size alert expanding from Dynamic Island with cancel and confirmation buttons 30 | - **[IslandMediumAlert](Sources/IslandAlertsForSwiftUI/IslandMediumAlert/README.md)**: a medium-size alert expanding into a top rectangle from the Dynamic Island, useful for small updates 31 | - **[IslandSquareAlert](Sources/IslandAlertsForSwiftUI/IslandSquareAlert/README.md)**: a small-size alert expanding into a square from the Dynamic Island, useful for quick animations 32 | 33 | ### Notch 34 | 35 | ![IslandAlerts](Resources/NotchAlerts.png) 36 | 37 | Let's not leave our dear old Notch behind! 38 | 39 | The package includes the same animations also for the Notch. They may not be as beautiful, but they are useful! 40 | 41 | - **[NotchLargeAlert](Sources/IslandAlertsForSwiftUI/NotchLargeAlert/README.md)**: same of IslandLargeAlert but for Notch 42 | - **[NotchMediumAlert](Sources/IslandAlertsForSwiftUI/NotchMediumAlert/README.md)**: same of IslandMediumAlert but for Notch 43 | 44 | ## Installation 45 | 46 | Required: 47 | - iOS 15.0 or above 48 | - Xcode 13.0 or above 49 | 50 | In Xcode go to `File -> Add Packages...` and paste in the repo's url: `https://github.com/alessiorubicini/IslandAlertsForSwiftUI`. 51 | f 52 | 53 | ## License 54 | 55 | Copyright 2022 (©) Alessio Rubicini. 56 | 57 | The license for this repository is MIT License. 58 | 59 | Please see the [LICENSE](LICENSE) file for full reference. 60 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchMediumAlert/NotchMediumAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchMediumAlert.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 21/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct NotchMediumAlert: ViewModifier { 12 | 13 | @Binding var isPresented: Bool 14 | let systemIcon: String 15 | let title: String 16 | 17 | public func body(content: Content) -> some View { 18 | ZStack { 19 | VStack { 20 | VStack { 21 | 22 | if isPresented { 23 | 24 | Group { 25 | Image(systemName: self.systemIcon) 26 | .font(.title) 27 | .foregroundColor(.white) 28 | .padding(.horizontal, 20) 29 | .padding(.top, 20) 30 | 31 | Text(title).font(.headline) 32 | .foregroundColor(.gray) 33 | .lineLimit(1) 34 | .multilineTextAlignment(.center) 35 | .padding(.vertical, 20) 36 | } 37 | .onTapGesture { 38 | withAnimation { 39 | isPresented.toggle() 40 | } 41 | } 42 | } 43 | 44 | } 45 | .islandAnimation(isPresented: $isPresented) 46 | .notchMediumFrame(isPresented: $isPresented) 47 | .background(Rectangle() 48 | .notchMediumFrame(isPresented: $isPresented) 49 | .foregroundColor(Color.black) 50 | .cornerRadius(isPresented ? 20:30, corners: [.bottomLeft, .bottomRight]) 51 | .padding(.bottom, isPresented ? 10:0)) 52 | 53 | Spacer() 54 | 55 | }.edgesIgnoringSafeArea(.vertical) 56 | 57 | content 58 | 59 | } 60 | } 61 | } 62 | 63 | extension View { 64 | /// A medium-size alert expanding into a top rectangle from the notch 65 | /// - Parameters: 66 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 67 | /// - systemIcon: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 68 | /// - text: A text string used as the text of the alert. 69 | public func notchMediumAlert(isPresented: Binding, systemIcon: String, title: String) -> some View { 70 | ModifiedContent(content: self, modifier: NotchMediumAlert(isPresented: isPresented, systemIcon: systemIcon, title: title)) 71 | } 72 | } 73 | 74 | struct NotchMediumAlertDebug_Previews: PreviewProvider { 75 | static var previews: some View { 76 | NotchMediumAlertExample() 77 | .previewDevice(PreviewDevice(rawValue: "iPhone 11")) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandSquareAlert/IslandSquareAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandSquareAlert.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct IslandSquareAlert: ViewModifier { 12 | @Binding var isPresented: Bool 13 | 14 | let systemIcon: String 15 | let text: String 16 | 17 | public func body(content: Content) -> some View { 18 | ZStack { 19 | VStack { 20 | VStack { 21 | 22 | if isPresented { 23 | 24 | Group { 25 | Image(systemName: self.systemIcon) 26 | .font(.system(size: 40)) 27 | .symbolRenderingMode(.multicolor) 28 | .padding(.horizontal, 20) 29 | .padding(.top, 40) 30 | 31 | Text(text).font(.headline) 32 | .foregroundColor(.white) 33 | .multilineTextAlignment(.center) 34 | .padding(.top, 10) 35 | 36 | //Spacer() 37 | } 38 | .onTapGesture { 39 | withAnimation { 40 | isPresented.toggle() 41 | } 42 | } 43 | 44 | } 45 | 46 | } 47 | .islandAnimation(isPresented: $isPresented) 48 | .islandSquareFrame(isPresented: $isPresented) 49 | .background(Rectangle() 50 | .islandSquareFrame(isPresented: $isPresented) 51 | .cornerRadius(isPresented ? 30 : 20) 52 | .foregroundColor(Color.black) 53 | .padding(.top, 22)) 54 | 55 | Spacer() 56 | 57 | }.edgesIgnoringSafeArea(.vertical) 58 | 59 | content 60 | 61 | } 62 | } 63 | } 64 | 65 | extension View { 66 | /// A small-size alert expanding into a square from the Dynamic Island. It's dismissed by tapping on it. 67 | /// - Parameters: 68 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 69 | /// - systemIcon: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 70 | /// - text: A text string used as the text of the alert. 71 | public func islandSquareAlert(isPresented: Binding, systemIcon: String, text: String) -> some View { 72 | ModifiedContent(content: self, modifier: IslandSquareAlert(isPresented: isPresented, systemIcon: systemIcon, text: text)) 73 | } 74 | } 75 | 76 | struct IslandSquareAlertDebug_Previews: PreviewProvider { 77 | static var previews: some View { 78 | IslandSquareAlertExample() 79 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandMediumAlert/IslandMediumAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandMediumAlert.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct IslandMediumAlert: ViewModifier { 12 | 13 | @Binding var isPresented: Bool 14 | let systemIcon: String 15 | let title: String 16 | 17 | public func body(content: Content) -> some View { 18 | ZStack { 19 | VStack { 20 | HStack { 21 | 22 | if isPresented { 23 | 24 | Image(systemName: self.systemIcon) 25 | .font(.title) 26 | .foregroundColor(.white) 27 | .padding(.horizontal, 20) 28 | .padding(.top, 20) 29 | 30 | Spacer() 31 | 32 | Text(title).font(.headline) 33 | .foregroundColor(.gray) 34 | .lineLimit(1) 35 | .multilineTextAlignment(.center) 36 | .padding(.top, 30) 37 | 38 | Spacer() 39 | 40 | Button { 41 | withAnimation { 42 | isPresented.toggle() 43 | } 44 | } label: { 45 | Image(systemName: "xmark.circle.fill") 46 | .font(.largeTitle) 47 | .foregroundColor(.gray) 48 | .symbolRenderingMode(.hierarchical) 49 | .padding(.horizontal, 20) 50 | .padding(.top, 20) 51 | } 52 | 53 | 54 | } 55 | 56 | } 57 | .islandAnimation(isPresented: $isPresented) 58 | .islandMediumFrame(isPresented: $isPresented) 59 | .background(Rectangle() 60 | .islandMediumFrame(isPresented: $isPresented) 61 | .foregroundColor(Color.black) 62 | .cornerRadius(isPresented ? 40 : 20) 63 | .padding(.top, 22)) 64 | Spacer() 65 | 66 | }.edgesIgnoringSafeArea(.vertical) 67 | 68 | content 69 | 70 | } 71 | } 72 | } 73 | 74 | extension View { 75 | /// A medium-size alert expanding into a top rectangle from the Dynamic Island 76 | /// - Parameters: 77 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 78 | /// - systemIcon: The name of the system symbol image. Use the SF Symbols app to look up the names of system symbol images. 79 | /// - text: A text string used as the text of the alert. 80 | public func islandMediumAlert(isPresented: Binding, systemIcon: String, title: String) -> some View { 81 | ModifiedContent(content: self, modifier: IslandMediumAlert(isPresented: isPresented, systemIcon: systemIcon, title: title)) 82 | } 83 | } 84 | 85 | struct IslandMediumAlertDebug_Previews: PreviewProvider { 86 | static var previews: some View { 87 | IslandMediumAlertExample() 88 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/NotchLargeAlert/NotchLargeAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotchLargeAlert.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct NotchLargeAlert: ViewModifier { 12 | 13 | @Binding var isPresented: Bool 14 | let title: String 15 | let message: String 16 | let action: () -> () 17 | 18 | public func body(content: Content) -> some View { 19 | ZStack { 20 | VStack { 21 | 22 | VStack { 23 | 24 | if isPresented { 25 | Group { 26 | 27 | Text(title).font(.headline) 28 | 29 | Text(message) 30 | .lineLimit(3) 31 | .padding(.top, 5) 32 | .padding(.horizontal, 20) 33 | 34 | }.foregroundColor(.white) 35 | .multilineTextAlignment(.center) 36 | 37 | HStack { 38 | Button(role: .destructive, action: { 39 | withAnimation { 40 | isPresented.toggle() 41 | } 42 | }) { 43 | Text("Cancel").frame(maxWidth: 100) 44 | } 45 | .buttonStyle(.borderedProminent) 46 | .cornerRadius(10) 47 | .padding(2) 48 | 49 | Button(action: {}) { 50 | Text("Confirm").frame(maxWidth: 100) 51 | } 52 | .buttonStyle(.borderedProminent) 53 | .cornerRadius(10) 54 | .padding(2) 55 | } 56 | 57 | } 58 | 59 | } 60 | .islandAnimation(isPresented: $isPresented) 61 | .notchLargeFrame(isPresented: $isPresented) 62 | .background(Rectangle() 63 | .notchLargeFrame(isPresented: $isPresented) 64 | .foregroundColor(Color.black) 65 | .cornerRadius(isPresented ? 20:30, corners: [.bottomLeft, .bottomRight]) 66 | .padding(.bottom, isPresented ? 10:0)) 67 | 68 | 69 | Spacer() 70 | 71 | }.edgesIgnoringSafeArea(.vertical) 72 | 73 | content 74 | 75 | } 76 | } 77 | } 78 | 79 | extension View { 80 | /// A large alert expanding from the top notch 81 | /// - Parameters: 82 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 83 | /// - title: A text string used as the title of the alert. 84 | /// - message: A text string used as the message of the alert, maximum 3 lines of text after which it is truncated 85 | /// - action: function performed when the Confirm button is pressed 86 | public func notchLargeAlert(isPresented: Binding, title: String, message: String, action: @escaping () -> ()) -> some View { 87 | ModifiedContent(content: self, modifier: NotchLargeAlert(isPresented: isPresented, title: title, message: message, action: action)) 88 | } 89 | } 90 | 91 | struct NotchLargeAlertDebug_Previews: PreviewProvider { 92 | static var previews: some View { 93 | NotchLargeAlertExample() 94 | .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro")) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/IslandLargeAlert/IslandLargeAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IslandLargeAlert.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct IslandLargeAlert: ViewModifier { 12 | 13 | @Binding var isPresented: Bool 14 | let title: String 15 | let message: String 16 | let action: () -> () 17 | 18 | public func body(content: Content) -> some View { 19 | ZStack { 20 | VStack { 21 | 22 | VStack { 23 | 24 | if isPresented { 25 | Group { 26 | 27 | Text(title).font(.headline) 28 | .padding(.top, 30) 29 | 30 | Text(message) 31 | .lineLimit(4) 32 | .padding(.top, 5) 33 | .padding(.horizontal, 30) 34 | 35 | }.foregroundColor(.white) 36 | .multilineTextAlignment(.center) 37 | 38 | HStack { 39 | Button(role: .destructive, action: { 40 | withAnimation { 41 | isPresented.toggle() 42 | } 43 | }) { 44 | Text("Cancel").frame(maxWidth: 100) 45 | } 46 | .buttonStyle(.borderedProminent) 47 | .cornerRadius(15) 48 | .padding(5) 49 | 50 | Button(action: { 51 | withAnimation { 52 | self.action() 53 | } 54 | }) { 55 | Text("Confirm").frame(maxWidth: 100) 56 | } 57 | .buttonStyle(.borderedProminent) 58 | .cornerRadius(15) 59 | .padding(5) 60 | } 61 | 62 | } 63 | 64 | } 65 | .islandAnimation(isPresented: $isPresented) 66 | .islandLargeFrame(isPresented: $isPresented) 67 | .background(Rectangle() 68 | .islandLargeFrame(isPresented: $isPresented) 69 | .foregroundColor(Color.black) 70 | .cornerRadius(isPresented ? 40 : 20) 71 | .padding(.top, 22)) 72 | 73 | 74 | Spacer() 75 | 76 | }.edgesIgnoringSafeArea(.vertical) 77 | 78 | content 79 | 80 | } 81 | } 82 | } 83 | 84 | extension View { 85 | 86 | /// A large alert expanding from Dynamic Island with cancel and confirmation buttons 87 | /// - Parameters: 88 | /// - isPresented: A binding to a Boolean value that determines whether to present the alert. When the user presses or taps one of the Cancel action, the system sets this value to false and dismisses. 89 | /// - title: A text string used as the title of the alert. 90 | /// - message: A text string used as the message of the alert, maximum 3 lines of text after which it is truncated 91 | /// - action: function performed when the Confirm button is pressed 92 | public func islandLargeAlert(isPresented: Binding, title: String, message: String, action: @escaping () -> ()) -> some View { 93 | ModifiedContent(content: self, modifier: IslandLargeAlert(isPresented: isPresented, title: title, message: message, action: action)) 94 | } 95 | } 96 | 97 | struct IslandLargeAlertDebug_Previews: PreviewProvider { 98 | static var previews: some View { 99 | IslandLargeAlertExample() 100 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro")) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/IslandAlertsForSwiftUI/Utils/ModelName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelName.swift 3 | // 4 | // 5 | // Created by Alessio Rubicini on 20/09/22. 6 | // 7 | // https://gist.github.com/soapyigu/478070fbf06b37205073ad939f285d2f#file-devcemodel-swift 8 | 9 | import Foundation 10 | import SystemConfiguration 11 | import UIKit 12 | 13 | public extension UIDevice { 14 | 15 | // Returns the model name of the current device 16 | static let modelName: String = { 17 | var systemInfo = utsname() 18 | uname(&systemInfo) 19 | let machineMirror = Mirror(reflecting: systemInfo.machine) 20 | let identifier = machineMirror.children.reduce("") { identifier, element in 21 | guard let value = element.value as? Int8, value != 0 else { return identifier } 22 | return identifier + String(UnicodeScalar(UInt8(value))) 23 | } 24 | 25 | func mapToDevice(identifier: String) -> String { 26 | #if os(iOS) 27 | switch identifier { 28 | case "iPod5,1": return "iPod Touch 5" 29 | case "iPod7,1": return "iPod Touch 6" 30 | case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" 31 | case "iPhone4,1": return "iPhone 4s" 32 | case "iPhone5,1", "iPhone5,2": return "iPhone 5" 33 | case "iPhone5,3", "iPhone5,4": return "iPhone 5c" 34 | case "iPhone6,1", "iPhone6,2": return "iPhone 5s" 35 | case "iPhone7,2": return "iPhone 6" 36 | case "iPhone7,1": return "iPhone 6 Plus" 37 | case "iPhone8,1": return "iPhone 6s" 38 | case "iPhone8,2": return "iPhone 6s Plus" 39 | case "iPhone9,1", "iPhone9,3": return "iPhone 7" 40 | case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" 41 | case "iPhone8,4": return "iPhone SE" 42 | case "iPhone10,1", "iPhone10,4": return "iPhone 8" 43 | case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" 44 | case "iPhone10,3", "iPhone10,6": return "iPhone X" 45 | case "iPhone11,2": return "iPhone XS" 46 | case "iPhone11,4", "iPhone11,6": return "iPhone XS Max" 47 | case "iPhone11,8": return "iPhone XR" 48 | case "iPhone12,1": return "iPhone 11" 49 | case "iPhone12,3": return "iPhone 11 Pro" 50 | case "iPhone12,5": return "iPhone 11 Pro Max" 51 | case "iPhone12,8": return "iPhone SE 2nd Gen" 52 | case "iPhone13,1": return "iPhone 12 Mini" 53 | case "iPhone13,2": return "iPhone 12" 54 | case "iPhone13,3": return "iPhone 12 Pro" 55 | case "iPhone13,4": return "iPhone 12 Pro Max" 56 | case "iPhone14,2": return "iPhone 13 Pro" 57 | case "iPhone14,3": return "iPhone 13 Pro Max" 58 | case "iPhone14,4": return "iPhone 13 Mini" 59 | case "iPhone14,5": return "iPhone 13" 60 | case "iPhone14,6": return "iPhone SE 3rd Gen" 61 | case "iPhone14,7": return "iPhone 14" 62 | case "iPhone14,8": return "iPhone 14 Plus" 63 | case "iPhone15,2": return "iPhone 14 Pro" 64 | case "iPhone15,3": return "iPhone 14 Pro Max" 65 | case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2" 66 | case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3" 67 | case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4" 68 | case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" 69 | case "iPad5,3", "iPad5,4": return "iPad Air 2" 70 | case "iPad6,11", "iPad6,12": return "iPad 5" 71 | case "iPad7,5", "iPad7,6": return "iPad 6" 72 | case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini" 73 | case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2" 74 | case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3" 75 | case "iPad5,1", "iPad5,2": return "iPad Mini 4" 76 | case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)" 77 | case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch)" 78 | case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)" 79 | case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)" 80 | case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return "iPad Pro (11-inch)" 81 | case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return "iPad Pro (12.9-inch) (3rd generation)" 82 | case "AppleTV5,3": return "Apple TV" 83 | case "AppleTV6,2": return "Apple TV 4K" 84 | case "AudioAccessory1,1": return "HomePod" 85 | case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" 86 | default: return identifier 87 | } 88 | #elseif os(tvOS) 89 | switch identifier { 90 | case "AppleTV5,3": return "Apple TV 4" 91 | case "AppleTV6,2": return "Apple TV 4K" 92 | case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" 93 | default: return identifier 94 | } 95 | #endif 96 | } 97 | 98 | return mapToDevice(identifier: identifier) 99 | }() 100 | } 101 | --------------------------------------------------------------------------------