diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46735f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.build/ +DerivedData/ +*.xcuserstate +xcuserdata/ +.swiftpm/ +Package.resolved +.claude diff --git a/Example/Sources/ContentView.swift b/Example/Sources/ContentView.swift new file mode 100644 index 0000000..d676702 --- /dev/null +++ b/Example/Sources/ContentView.swift @@ -0,0 +1,42 @@ +import SwiftUI +import Playgrounds +import TopNotch + +@main struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} + +struct ContentView: View { + @Environment(\.scenePhase) private var scenePhase + var body: some View { + Text("Hello world!") + .padding() + .onChange(of: scenePhase) { + if scenePhase == .active { + showTopNotch() + } + } + } + + func showTopNotch() { + // Create a custom view (or use your own) to display behind the notch. + let notchLabel = UILabel() + notchLabel.text = "TopNotch" + notchLabel.textAlignment = .center + notchLabel.textColor = .white + notchLabel.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.7) + + // Show the notch view. + TopNotchManager.shared.show(customView: notchLabel, with: .init(animationDuration: 0.3, + shouldAnimate: true, + shouldHideForTaskSwitcher: false)) + } +} + +#Preview { + ContentView() +} diff --git a/Example/demo.xcodeproj/project.pbxproj b/Example/demo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ae844bf --- /dev/null +++ b/Example/demo.xcodeproj/project.pbxproj @@ -0,0 +1,378 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 90; + objects = { + +/* Begin PBXBuildFile section */ + 8DDFC4352FF7B9B00012A570 /* TopNotch in Frameworks */ = {isa = PBXBuildFile; productRef = 8DDFC4342FF7B9B00012A570 /* TopNotch */; }; + 8DDFC43C2FF7BFB60012A570 /* TopNotch in Frameworks */ = {isa = PBXBuildFile; productRef = 8DDFC43B2FF7BFB60012A570 /* TopNotch */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 000000000000000000000120 /* demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 000000000000000000000010 /* Sources */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Sources; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 000000000000000130000000 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + files = ( + 8DDFC4352FF7B9B00012A570 /* TopNotch in Frameworks */, + 8DDFC43C2FF7BFB60012A570 /* TopNotch in Frameworks */, + ); + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 000000000000000000000001 = { + isa = PBXGroup; + children = ( + 000000000000000000000010 /* Sources */, + 000000000000000000000020 /* Products */, + ); + sourceTree = ""; + }; + 000000000000000000000020 /* Products */ = { + isa = PBXGroup; + children = ( + 000000000000000000000120 /* demo.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 000000000000000100000000 /* demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 000000000000000110000000 /* Build configuration list for PBXNativeTarget "demo" */; + buildPhases = ( + 000000000000000120000000 /* Sources */, + 000000000000000130000000 /* Frameworks */, + 000000000000000140000000 /* Resources */, + ); + buildRules = ( + ); + fileSystemSynchronizedGroups = ( + 000000000000000000000010 /* Sources */, + ); + name = demo; + packageProductDependencies = ( + 8DDFC4342FF7B9B00012A570 /* TopNotch */, + 8DDFC43B2FF7BFB60012A570 /* TopNotch */, + ); + productName = MyApp; + productReference = 000000000000000000000120 /* demo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 000000000000000000000000 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2630; + LastUpgradeCheck = 2700; + TargetAttributes = { + 000000000000000100000000 = { + CreatedOnToolsVersion = 26.3; + }; + }; + }; + buildConfigurationList = 000000000000000010000000 /* Build configuration list for PBXProject "demo" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 000000000000000000000001; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 8DDFC43A2FF7BFB60012A570 /* XCLocalSwiftPackageReference "../../TopNotch" */, + ); + preferredProjectObjectVersion = 90; + productRefGroup = 000000000000000000000020 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 000000000000000100000000 /* demo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 000000000000000140000000 /* Resources */ = { + isa = PBXResourcesBuildPhase; + files = ( + ); + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 000000000000000120000000 /* Sources */ = { + isa = PBXSourcesBuildPhase; + files = ( + ); + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 000000000000000011000000 /* Debug configuration for PBXProject "demo" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLETVOS_DEPLOYMENT_TARGET = 27.0; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DRIVERKIT_DEPLOYMENT_TARGET = 27.0; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 27.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 27.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PROJECT_UNIQUE_VALUE = FHI928ZR; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + WATCHOS_DEPLOYMENT_TARGET = 27.0; + XROS_DEPLOYMENT_TARGET = 27.0; + }; + name = Debug; + }; + 000000000000000012000000 /* Release configuration for PBXProject "demo" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLETVOS_DEPLOYMENT_TARGET = 27.0; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DRIVERKIT_DEPLOYMENT_TARGET = 27.0; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 27.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 27.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PROJECT_UNIQUE_VALUE = FHI928ZR; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_COMPILATION_MODE = wholemodule; + WATCHOS_DEPLOYMENT_TARGET = 27.0; + XROS_DEPLOYMENT_TARGET = 27.0; + }; + name = Release; + }; + 000000000000000111000000 /* Debug configuration for PBXNativeTarget "demo" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "devplaceholder.$(PROJECT_UNIQUE_VALUE:identifier).$(PRODUCT_NAME:identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 000000000000000112000000 /* Release configuration for PBXNativeTarget "demo" */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "devplaceholder.$(PROJECT_UNIQUE_VALUE:identifier).$(PRODUCT_NAME:identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 000000000000000010000000 /* Build configuration list for PBXProject "demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 000000000000000011000000 /* Debug configuration for PBXProject "demo" */, + 000000000000000012000000 /* Release configuration for PBXProject "demo" */, + ); + defaultConfigurationName = Release; + }; + 000000000000000110000000 /* Build configuration list for PBXNativeTarget "demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 000000000000000111000000 /* Debug configuration for PBXNativeTarget "demo" */, + 000000000000000112000000 /* Release configuration for PBXNativeTarget "demo" */, + ); + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 8DDFC43A2FF7BFB60012A570 /* XCLocalSwiftPackageReference "../../TopNotch" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../TopNotch; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 8DDFC4342FF7B9B00012A570 /* TopNotch */ = { + isa = XCSwiftPackageProductDependency; + productName = TopNotch; + }; + 8DDFC43B2FF7BFB60012A570 /* TopNotch */ = { + isa = XCSwiftPackageProductDependency; + productName = TopNotch; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 000000000000000000000000 /* Project object */; +} diff --git a/TopNotch.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from TopNotch.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Example/demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..3bb2dc7 --- /dev/null +++ b/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "TopNotch", + platforms: [ + .iOS(.v13), + ], + products: [ + .library(name: "TopNotch", targets: ["TopNotch"]), + ], + targets: [ + .target(name: "TopNotch"), + ] +) diff --git a/README.md b/README.md deleted file mode 120000 index 2ef36d7..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -./TopNotch/TopNotch.docc/TopNotch.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5364c26 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# ``TopNotch`` + +![TopNotch. Hide a message under the notch. It'll be our little secret.](/Resources/banner.png) + +## Overview + +TopNotch is a lil Swift package that lets you **hide a custom view underneath the device’s notch**. Since it’ll only be visible in screenshots and screen recordings, you can have some fun. **Put a version string in there**, maybe use it as a branding moment to **stick your logo there**, write your darkest secrets, whatever your little heart desires. + +If you opt to actually write your little secrets there, you can set `shouldHideForTaskSwitcher` on `TopNotchConfiguration` to hide the view when `sceneWillDeactivateNotification` gets called. It'll come back after `sceneDidActivateNotification` is called or if you ask nicely. + +It automatically calculates the notch’s exclusion area (using an undocumented `_exclusionArea` property on UIScreen). Since it doesn't always return the right values for older notch styles, I'm applying some manual, device-specific adjustments to make sure it stays hidden on all devices. + +> [!WARNING] +> Because TopNotch relies on undocumented APIs, it may not be App Store safe. Give it a shot though. I dare you. + +https://github.com/user-attachments/assets/61dfcdc5-868a-45d6-91f1-aa0898211357 + + +## TODO +- Reduce logging pollution +- Make the SwiftUI adapter useful + +## Usage + +I've attached a little demo project if that's how you roll. TL;DR: + +```swift +struct ContentView: View { + @Environment(\.scenePhase) private var scenePhase + var body: some View { + Text("Hello world!") + .padding() + .onChange(of: scenePhase) { + if scenePhase == .active { + showTopNotch() + } + } + } + + func showTopNotch() { + // Create a custom view (or use your own) to display behind the notch. + let notchLabel = UILabel() + notchLabel.text = "TopNotch" + notchLabel.textAlignment = .center + notchLabel.textColor = .white + notchLabel.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.7) + + // Show the notch view. + TopNotchManager.shared.show(customView: notchLabel, with: .init(animationDuration: 0.3, + shouldAnimate: true, + shouldHideForTaskSwitcher: false)) + } +} +// To hide it: +TopNotchManager.shared.hide() +``` diff --git a/TopNotch/TopNotch.docc/Resources/banner.png b/Resources/banner.png similarity index 100% rename from TopNotch/TopNotch.docc/Resources/banner.png rename to Resources/banner.png diff --git a/TopNotch/TopNotch.docc/Resources/demo.mp4 b/Resources/demo.mp4 similarity index 100% rename from TopNotch/TopNotch.docc/Resources/demo.mp4 rename to Resources/demo.mp4 diff --git a/TopNotch/TopNotch.docc/Resources/poster.jpg b/Resources/poster.jpg similarity index 100% rename from TopNotch/TopNotch.docc/Resources/poster.jpg rename to Resources/poster.jpg diff --git a/TopNotch/TopNotchConfiguration.swift b/Sources/TopNotchConfiguration.swift similarity index 100% rename from TopNotch/TopNotchConfiguration.swift rename to Sources/TopNotchConfiguration.swift diff --git a/TopNotch/TopNotchManager.swift b/Sources/TopNotchManager.swift similarity index 88% rename from TopNotch/TopNotchManager.swift rename to Sources/TopNotchManager.swift index 8a7906c..bebfff5 100644 --- a/TopNotch/TopNotchManager.swift +++ b/Sources/TopNotchManager.swift @@ -122,8 +122,8 @@ public final class TopNotchManager { notchView?.alpha = 0 updateNotchFrame() - // Animate fade-in. - UIView.animate(withDuration: config.animationDuration) { + // Fade in (or apply instantly when animation is disabled). + applyChanges { self.notchView?.alpha = 1.0 } print("[TopNotchManager] Notch view fading in") @@ -152,7 +152,7 @@ public final class TopNotchManager { public func hide() { print("[TopNotchManager] Hiding notch view") NotificationCenter.default.removeObserver(self) - UIView.animate(withDuration: config.animationDuration, animations: { + applyChanges({ self.notchView?.alpha = 0 }) { _ in print("[TopNotchManager] Notch view removed from superview") @@ -162,7 +162,22 @@ public final class TopNotchManager { } // MARK: Private Helpers - + + /// Runs `animations` with a fade, honoring `config.shouldAnimate`. + /// When animation is disabled the changes are applied instantly and + /// `completion` still fires so teardown logic runs identically. + private func applyChanges(_ animations: @escaping () -> Void, + completion: ((Bool) -> Void)? = nil) { + guard config.shouldAnimate else { + animations() + completion?(true) + return + } + UIView.animate(withDuration: config.animationDuration, + animations: animations, + completion: completion) + } + /// Creates a default notch view with a red tint. /// /// - Returns: A default `UIView` instance to be used as the notch view. @@ -220,7 +235,7 @@ public final class TopNotchManager { let newHeight = rawRect.height * override.heightFactor adjustedFrame = CGRect(x: newX, y: rawRect.origin.y, width: newWidth, height: newHeight) print("[TopNotchManager] Applied individual override for \(modelId): \(adjustedFrame)") - applyCornerStyling(with: override.radius, roundBottomOnly: true) + applyCornerStyling(with: override.radius, bounds: adjustedFrame, roundBottomOnly: true) } // Otherwise, check for series overrides. else if let seriesOverride = modelSeriesOverrides.first(where: { modelId.hasPrefix($0.key) }) { @@ -230,18 +245,18 @@ public final class TopNotchManager { let newHeight = rawRect.height * override.heightFactor adjustedFrame = CGRect(x: newX, y: rawRect.origin.y, width: newWidth, height: newHeight) print("[TopNotchManager] Applied series override for \(modelId) (prefix \(seriesOverride.key)): \(adjustedFrame)") - applyCornerStyling(with: override.radius, roundBottomOnly: true) + applyCornerStyling(with: override.radius, bounds: adjustedFrame, roundBottomOnly: true) } // Otherwise, use default styling. else { adjustedFrame = rawRect if rawRect.origin.y > 0 { let capsuleRadius = rawRect.height / 2 - applyCornerStyling(with: capsuleRadius) + applyCornerStyling(with: capsuleRadius, bounds: adjustedFrame) print("[TopNotchManager] Dynamic island default styling (radius \(capsuleRadius))") } else { // Use a slightly increased radius for original notches to prevent clipping. - applyCornerStyling(with: 21, roundBottomOnly: true) + applyCornerStyling(with: 21, bounds: adjustedFrame, roundBottomOnly: true) print("[TopNotchManager] Original notch default styling (radius 21)") } } @@ -254,23 +269,25 @@ public final class TopNotchManager { /// /// - Parameters: /// - radius: The corner radius to apply. + /// - bounds: The size to draw the mask against, in the view's own coordinate space. /// - roundBottomOnly: If `true`, only the bottom corners will be rounded. - private func applyCornerStyling(with radius: CGFloat, roundBottomOnly: Bool = false) { + private func applyCornerStyling(with radius: CGFloat, bounds: CGRect, roundBottomOnly: Bool = false) { guard let view = notchView else { return } // Disable default clipping. view.clipsToBounds = false - + + let rect = CGRect(origin: .zero, size: bounds.size) let path: UIBezierPath if roundBottomOnly { - path = UIBezierPath(roundedRect: view.bounds, + path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: radius, height: radius)) } else { - path = UIBezierPath(roundedRect: view.bounds, cornerRadius: radius) + path = UIBezierPath(roundedRect: rect, cornerRadius: radius) } - + let maskLayer = CAShapeLayer() - maskLayer.frame = view.bounds + maskLayer.frame = rect maskLayer.path = path.cgPath view.layer.mask = maskLayer } @@ -281,7 +298,7 @@ public final class TopNotchManager { @objc private func sceneWillDeactivateNotification(_ notification: Notification) { if config.shouldHideForTaskSwitcher && isNotchVisible { print("[TopNotchManager] Hiding notch view for task switcher (willDeactivate)") - UIView.animate(withDuration: config.animationDuration) { + applyChanges { self.notchView?.alpha = 0 } } @@ -291,7 +308,7 @@ public final class TopNotchManager { @objc private func sceneDidActivateNotification(_ notification: Notification) { if config.shouldHideForTaskSwitcher && isNotchVisible { print("[TopNotchManager] Showing notch view after task switcher (didActivate)") - UIView.animate(withDuration: config.animationDuration) { + applyChanges { self.notchView?.alpha = 1.0 } } diff --git a/TopNotch/TopNotchModifier.swift b/Sources/TopNotchModifier.swift similarity index 100% rename from TopNotch/TopNotchModifier.swift rename to Sources/TopNotchModifier.swift diff --git a/TopNotch/UIDevice+Model.swift b/Sources/UIDevice+Model.swift similarity index 100% rename from TopNotch/UIDevice+Model.swift rename to Sources/UIDevice+Model.swift diff --git a/TopNotch.xcodeproj/project.pbxproj b/TopNotch.xcodeproj/project.pbxproj deleted file mode 100644 index 669f8ba..0000000 --- a/TopNotch.xcodeproj/project.pbxproj +++ /dev/null @@ -1,547 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - EAB4C7DE2D5AAA26000CE23F /* TopNotch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAB4C7AF2D5AA7EB000CE23F /* TopNotch.framework */; }; - EAB4C7DF2D5AAA26000CE23F /* TopNotch.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EAB4C7AF2D5AA7EB000CE23F /* TopNotch.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - EAB4C7E02D5AAA26000CE23F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = EAB4C7A62D5AA7EB000CE23F /* Project object */; - proxyType = 1; - remoteGlobalIDString = EAB4C7AE2D5AA7EB000CE23F; - remoteInfo = TopNotch; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - EAB4C7E22D5AAA26000CE23F /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - EAB4C7DF2D5AAA26000CE23F /* TopNotch.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - EAB4C7AF2D5AA7EB000CE23F /* TopNotch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TopNotch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EAB4C7C82D5AA990000CE23F /* TopNotchDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TopNotchDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - EAB4C7B62D5AA7EB000CE23F /* Exceptions for "TopNotch" folder in "TopNotch" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - publicHeaders = ( - TopNotch.h, - ); - target = EAB4C7AE2D5AA7EB000CE23F /* TopNotch */; - }; - EAB4C7D92D5AA992000CE23F /* Exceptions for "TopNotchDemo" folder in "TopNotchDemo" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - ); - target = EAB4C7C72D5AA990000CE23F /* TopNotchDemo */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - EAB4C7B12D5AA7EB000CE23F /* TopNotch */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - EAB4C7B62D5AA7EB000CE23F /* Exceptions for "TopNotch" folder in "TopNotch" target */, - ); - path = TopNotch; - sourceTree = ""; - }; - EAB4C7C92D5AA990000CE23F /* TopNotchDemo */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - EAB4C7D92D5AA992000CE23F /* Exceptions for "TopNotchDemo" folder in "TopNotchDemo" target */, - ); - path = TopNotchDemo; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - EAB4C7AC2D5AA7EB000CE23F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EAB4C7C52D5AA990000CE23F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - EAB4C7DE2D5AAA26000CE23F /* TopNotch.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - EAB4C7A52D5AA7EB000CE23F = { - isa = PBXGroup; - children = ( - EAB4C7B12D5AA7EB000CE23F /* TopNotch */, - EAB4C7C92D5AA990000CE23F /* TopNotchDemo */, - EAB4C7DD2D5AAA26000CE23F /* Frameworks */, - EAB4C7B02D5AA7EB000CE23F /* Products */, - ); - sourceTree = ""; - }; - EAB4C7B02D5AA7EB000CE23F /* Products */ = { - isa = PBXGroup; - children = ( - EAB4C7AF2D5AA7EB000CE23F /* TopNotch.framework */, - EAB4C7C82D5AA990000CE23F /* TopNotchDemo.app */, - ); - name = Products; - sourceTree = ""; - }; - EAB4C7DD2D5AAA26000CE23F /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - EAB4C7AA2D5AA7EB000CE23F /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - EAB4C7AE2D5AA7EB000CE23F /* TopNotch */ = { - isa = PBXNativeTarget; - buildConfigurationList = EAB4C7B72D5AA7EB000CE23F /* Build configuration list for PBXNativeTarget "TopNotch" */; - buildPhases = ( - EAB4C7AA2D5AA7EB000CE23F /* Headers */, - EAB4C7AB2D5AA7EB000CE23F /* Sources */, - EAB4C7AC2D5AA7EB000CE23F /* Frameworks */, - EAB4C7AD2D5AA7EB000CE23F /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - EAB4C7B12D5AA7EB000CE23F /* TopNotch */, - ); - name = TopNotch; - packageProductDependencies = ( - ); - productName = TopNotch; - productReference = EAB4C7AF2D5AA7EB000CE23F /* TopNotch.framework */; - productType = "com.apple.product-type.framework"; - }; - EAB4C7C72D5AA990000CE23F /* TopNotchDemo */ = { - isa = PBXNativeTarget; - buildConfigurationList = EAB4C7DA2D5AA992000CE23F /* Build configuration list for PBXNativeTarget "TopNotchDemo" */; - buildPhases = ( - EAB4C7C42D5AA990000CE23F /* Sources */, - EAB4C7C52D5AA990000CE23F /* Frameworks */, - EAB4C7C62D5AA990000CE23F /* Resources */, - EAB4C7E22D5AAA26000CE23F /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - EAB4C7E12D5AAA26000CE23F /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - EAB4C7C92D5AA990000CE23F /* TopNotchDemo */, - ); - name = TopNotchDemo; - packageProductDependencies = ( - ); - productName = TopNotchDemo; - productReference = EAB4C7C82D5AA990000CE23F /* TopNotchDemo.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EAB4C7A62D5AA7EB000CE23F /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1610; - LastUpgradeCheck = 1610; - TargetAttributes = { - EAB4C7AE2D5AA7EB000CE23F = { - CreatedOnToolsVersion = 16.1; - }; - EAB4C7C72D5AA990000CE23F = { - CreatedOnToolsVersion = 16.1; - }; - }; - }; - buildConfigurationList = EAB4C7A92D5AA7EB000CE23F /* Build configuration list for PBXProject "TopNotch" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = EAB4C7A52D5AA7EB000CE23F; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = EAB4C7B02D5AA7EB000CE23F /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - EAB4C7AE2D5AA7EB000CE23F /* TopNotch */, - EAB4C7C72D5AA990000CE23F /* TopNotchDemo */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - EAB4C7AD2D5AA7EB000CE23F /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EAB4C7C62D5AA990000CE23F /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - EAB4C7AB2D5AA7EB000CE23F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EAB4C7C42D5AA990000CE23F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - EAB4C7E12D5AAA26000CE23F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = EAB4C7AE2D5AA7EB000CE23F /* TopNotch */; - targetProxy = EAB4C7E02D5AAA26000CE23F /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - EAB4C7B82D5AA7EB000CE23F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUILD_LIBRARY_FOR_DISTRIBUTION = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = CG56CG5WCQ; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - PRODUCT_BUNDLE_IDENTIFIER = gold.samhenri.TopNotch; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_INSTALL_OBJC_HEADER = NO; - SWIFT_PACKAGE_NAME = "$(TARGET_NAME:c99extidentifier)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - EAB4C7B92D5AA7EB000CE23F /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUILD_LIBRARY_FOR_DISTRIBUTION = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = CG56CG5WCQ; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - PRODUCT_BUNDLE_IDENTIFIER = gold.samhenri.TopNotch; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_INSTALL_OBJC_HEADER = NO; - SWIFT_PACKAGE_NAME = "$(TARGET_NAME:c99extidentifier)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - EAB4C7BA2D5AA7EB000CE23F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - EAB4C7BB2D5AA7EB000CE23F /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.1; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - EAB4C7DB2D5AA992000CE23F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = CG56CG5WCQ; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = TopNotchDemo/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = gold.samhenri.TopNotchDemo; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - EAB4C7DC2D5AA992000CE23F /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = CG56CG5WCQ; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = TopNotchDemo/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = gold.samhenri.TopNotchDemo; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - EAB4C7A92D5AA7EB000CE23F /* Build configuration list for PBXProject "TopNotch" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAB4C7BA2D5AA7EB000CE23F /* Debug */, - EAB4C7BB2D5AA7EB000CE23F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EAB4C7B72D5AA7EB000CE23F /* Build configuration list for PBXNativeTarget "TopNotch" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAB4C7B82D5AA7EB000CE23F /* Debug */, - EAB4C7B92D5AA7EB000CE23F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EAB4C7DA2D5AA992000CE23F /* Build configuration list for PBXNativeTarget "TopNotchDemo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAB4C7DB2D5AA992000CE23F /* Debug */, - EAB4C7DC2D5AA992000CE23F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = EAB4C7A62D5AA7EB000CE23F /* Project object */; -} diff --git a/TopNotch.xcodeproj/xcuserdata/shg.xcuserdatad/xcschemes/xcschememanagement.plist b/TopNotch.xcodeproj/xcuserdata/shg.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 4f73ec7..0000000 --- a/TopNotch.xcodeproj/xcuserdata/shg.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - SchemeUserState - - TopNotch.xcscheme_^#shared#^_ - - orderHint - 1 - - TopNotchDemo.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/TopNotch/TopNotch.docc/TopNotch.md b/TopNotch/TopNotch.docc/TopNotch.md deleted file mode 100644 index 17b3e4c..0000000 --- a/TopNotch/TopNotch.docc/TopNotch.md +++ /dev/null @@ -1,42 +0,0 @@ -# ``TopNotch`` - -![TopNotch. Hide a message under the notch. It'll be our little secret.](./TopNotch/TopNotch.docc/Resources/banner.png) - -## Overview - -TopNotch is a lil Swift package that lets you **hide a custom view underneath the device’s notch**. Since it’ll only be visible in screenshots and screen recordings, you can have some fun. **Put a version string in there**, maybe use it as a branding moment to **stick your logo there**, write your darkest secrets, whatever your little heart desires. - -If you opt to actually write your little secrets there, you can set `shouldHideForTaskSwitcher` on `TopNotchConfiguration` to hide the view when `sceneWillDeactivateNotification` gets called. It'll come back after `sceneDidActivateNotification` is called or if you ask nicely. - -It automatically calculates the notch’s exclusion area (using an undocumented `_exclusionArea` property on UIScreen). Since it doesn't always return the right values for older notch styles, I'm applying some manual, device-specific adjustments to make sure it stays hidden on all devices. - -> [!WARNING] -> Because TopNotch relies on undocumented APIs, it may not be App Store safe. Give it a shot though. I dare you. - -https://github.com/user-attachments/assets/61dfcdc5-868a-45d6-91f1-aa0898211357 - - -## TODO -- Reduce logging pollution -- Make the SwiftUI adapter useful - -## Usage - -I've attached a little demo project if that's how you roll. TL;DR: - -```swift -// Create a custom view (or use your own) to display behind the notch. -let notchLabel = UILabel() -notchLabel.text = "Hi Mom" -notchLabel.textAlignment = .center -notchLabel.textColor = .white -notchLabel.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.7) - -// Show the notch view. -TopNotchManager.shared.show(customView: notchLabel, with: TopNotchConfiguration(animationDuration: 0.3, - shouldAnimate: true, - shouldHideForTaskSwitcher: true)) - -// To hide it: -TopNotchManager.shared.hide() -``` diff --git a/TopNotch/TopNotch.h b/TopNotch/TopNotch.h deleted file mode 100644 index fdaa385..0000000 --- a/TopNotch/TopNotch.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// TopNotch.h -// TopNotch -// -// Created by Sam Gold on 2025-02-10. -// - -#import - -//! Project version number for TopNotch. -FOUNDATION_EXPORT double TopNotchVersionNumber; - -//! Project version string for TopNotch. -FOUNDATION_EXPORT const unsigned char TopNotchVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/TopNotchDemo/AppDelegate.swift b/TopNotchDemo/AppDelegate.swift deleted file mode 100644 index 7e35c7e..0000000 --- a/TopNotchDemo/AppDelegate.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// AppDelegate.swift -// TopNotchDemo -// -// Created by Sam Gold on 2025-02-10. -// - -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - return true - } - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } -} diff --git a/TopNotchDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/TopNotchDemo/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/TopNotchDemo/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/TopNotchDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/TopNotchDemo/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 2305880..0000000 --- a/TopNotchDemo/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/TopNotchDemo/Assets.xcassets/Contents.json b/TopNotchDemo/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/TopNotchDemo/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/TopNotchDemo/FontCollectionViewController.swift b/TopNotchDemo/FontCollectionViewController.swift deleted file mode 100644 index 81a59bd..0000000 --- a/TopNotchDemo/FontCollectionViewController.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// FontTableViewController.swift -// TopNotchDemo -// -// Created by Sam Gold on 2025-02-10. -// - -import UIKit -import TopNotch - -class FontTableViewController: UITableViewController { - - private let fontFamilies = UIFont.familyNames - - override init(style: UITableView.Style = .insetGrouped) { - super.init(style: style) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - self.title = "Font Families" - - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "FontCell") - - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Show Modal", style: .plain, target: self, action: #selector(showModal)) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - let watermarkConfig = TopNotchConfiguration(animationDuration: 0.3, - shouldAnimate: true, - shouldHideForTaskSwitcher: false) - - let notchLabel = UILabel() - notchLabel.text = "Top Notch!" - notchLabel.textColor = .white - notchLabel.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.7) - notchLabel.textAlignment = .center - - TopNotchManager.shared.show(customView: notchLabel, with: watermarkConfig) - } - - - // MARK: - Table view data source - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return fontFamilies.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCell(withIdentifier: "FontCell", for: indexPath) - cell.textLabel?.text = fontFamilies[indexPath.row] - return cell - } - - // MARK: - Actions - - @objc private func showModal() { - let modalVC = ModalSheetViewController() - let nav = UINavigationController(rootViewController: modalVC) - nav.modalPresentationStyle = .automatic - present(nav, animated: true, completion: nil) - } -} diff --git a/TopNotchDemo/Info.plist b/TopNotchDemo/Info.plist deleted file mode 100644 index 0eb786d..0000000 --- a/TopNotchDemo/Info.plist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - - - diff --git a/TopNotchDemo/ModalSheetViewController.swift b/TopNotchDemo/ModalSheetViewController.swift deleted file mode 100644 index 7f66053..0000000 --- a/TopNotchDemo/ModalSheetViewController.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ModalSheetViewController.swift -// TopNotchDemo -// -// Created by Sam Gold on 2025-02-10. -// - -import UIKit - -class ModalSheetViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .systemGroupedBackground - title = "Modal Sheet" - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, - target: self, - action: #selector(dismissModal)) - } - - @objc private func dismissModal() { - dismiss(animated: true, completion: nil) - } -} diff --git a/TopNotchDemo/SceneDelegate.swift b/TopNotchDemo/SceneDelegate.swift deleted file mode 100644 index 1697f87..0000000 --- a/TopNotchDemo/SceneDelegate.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// SceneDelegate.swift -// TopNotchDemo -// -// Created by Sam Gold on 2025-02-10. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } - - let window = UIWindow(windowScene: windowScene) - // Use the insetGrouped table view controller. - let fontTableVC = FontTableViewController() - let navController = UINavigationController(rootViewController: fontTableVC) - - window.rootViewController = navController - self.window = window - window.makeKeyAndVisible() - } -}