Skip to content

Commit

Permalink
feat: changing a better approach
Browse files Browse the repository at this point in the history
  • Loading branch information
markgravity committed Jan 19, 2024
1 parent 0677aa2 commit 9da79b2
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 144 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription

let package = Package(
name: "StatusBarStylist",
platforms: [.iOS(.v14), .macOS(.v13)],
platforms: [.iOS(.v15), .macOS(.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
Expand Down
26 changes: 5 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# StatusBarStylist

StatusBarStylist is a framework to supports changing the Status Bar style with natural SwiftUI syntax.
StatusBarStylist is a framework to support changing the Status Bar style with natural SwiftUI syntax.

Thank [Philip Trauner](https://stackoverflow.com/a/71458976/10270556) for this source code and idea.
Thanks [@Thieurom](https://github.com/Thieurom) for this perfect solution and source code. 😍

## Installation
Requirements iOS 14+
Expand All @@ -20,23 +20,7 @@ Requirements iOS 14+

## Usage

In your ```@main``` App file, simply wrap your main view in a ```RootView```.

```swift
@main
struct ProjectApp: App {
var body: some Scene {
WindowGroup {
RootView {
ContentView() // Change your content here 👈
}
}
}
}
```

### Example
Use the ```.statusBarStyle(_ style: UIStatusBarStyle)``` method on a View.
Use the ```.preferredStatusBarStyle(_ style: UIStatusBarStyle)``` method on a View.
```swift
struct ContentView: View {
var body: some View {
Expand All @@ -45,14 +29,14 @@ struct ContentView: View {
Color.black
.edgesIgnoringSafeArea(.all)
.overlay(Text("Light Status Bar").foregroundColor(.white))
.statusBarStyle(.lightContent) // set status bar style here
.preferredStatusBarStyle(.lightContent) // set status bar style here
.tabItem { Text("Tab 1") }

// Tab 2 will have a dark status bar
Color.white
.edgesIgnoringSafeArea(.all)
.overlay(Text("Dark Status Bar"))
.statusBarStyle(.darkContent) // set status bar style here
.preferredStatusBarStyle(.darkContent) // set status bar style here
.tabItem { Text("Tab 2") }
}
}
Expand Down
28 changes: 0 additions & 28 deletions Sources/StatusBarStylist/Interposed.swift

This file was deleted.

156 changes: 62 additions & 94 deletions Sources/StatusBarStylist/StatusBarStylist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,125 +2,93 @@
// https://docs.swift.org/swift-book

import SwiftUI
import UIKit

extension UIApplication {

var keyWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap(\.windows)
.first {
$0.isKeyWindow
}
}
}
private struct StatusBarViewModifier: ViewModifier {

extension UIViewController {
// This `currentStatusBarStyle` is needed to make the modifier work when
// a view is presented as a `sheet` or `fullScreenCover`.
private var currentStatusBarStyle: UIStatusBarStyle?
private let preferredStatusBarStyle: UIStatusBarStyle
private var statusBarWindow: UIWindow?

enum Holder {
static var statusBarStyleStack: [UIStatusBarStyle] = .init()
private var statusBarViewController: StatusBarViewController? {
statusBarWindow?.rootViewController as? StatusBarViewController
}

func interpose() -> Bool {
let sel1: Selector = #selector(
getter: preferredStatusBarStyle
)
let sel2: Selector = #selector(
getter: preferredStatusBarStyleModified
)
@SwiftUI.Environment(\.isPresented)
private var isPresented

let original = class_getInstanceMethod(Self.self, sel1)
let new = class_getInstanceMethod(Self.self, sel2)
init(style: UIStatusBarStyle) {
preferredStatusBarStyle = style

if let original = original, let new = new {
method_exchangeImplementations(original, new)

return true
}

return false
statusBarWindow = UIApplication.shared.statusBarWindow
currentStatusBarStyle = statusBarViewController?.statusBarStyle
statusBarViewController?.statusBarStyle = preferredStatusBarStyle
}

@objc dynamic var preferredStatusBarStyleModified: UIStatusBarStyle {
Holder.statusBarStyleStack.last ?? .default
func body(content: Content) -> some View {
content
.onAppear {
statusBarViewController?.statusBarStyle = preferredStatusBarStyle
}
.onDisappear {
if isPresented, let currentStatusBarStyle {
statusBarViewController?.statusBarStyle = currentStatusBarStyle
}
}
}
}

struct StatusBarStyle: ViewModifier {
private class StatusBarViewController: UIViewController {

@Environment(\.interposed) private var interposed

let statusBarStyle: UIStatusBarStyle
let animationDuration: TimeInterval

private func setStatusBarStyle(_ statusBarStyle: UIStatusBarStyle) {
UIViewController.Holder.statusBarStyleStack.append(statusBarStyle)

UIView.animate(withDuration: animationDuration) {
UIApplication.shared.keyWindow?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
var statusBarStyle: UIStatusBarStyle = .default {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}

func body(content: Content) -> some View {
content
.onAppear {
setStatusBarStyle(statusBarStyle)
}
.onChange(of: statusBarStyle) {
_ = UIViewController.Holder.statusBarStyleStack.removeLast()
setStatusBarStyle($0)
}
.onDisappear {
_ = UIViewController.Holder.statusBarStyleStack.removeLast()
override var preferredStatusBarStyle: UIStatusBarStyle {
statusBarStyle
}

UIView.animate(withDuration: animationDuration) {
UIApplication.shared.keyWindow?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
// Interposing might still be pending on initial render
.onChange(of: interposed) { _ in
UIView.animate(withDuration: animationDuration) {
UIApplication.shared.keyWindow?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
init() {
super.init(nibName: nil, bundle: nil)
view.backgroundColor = .clear
view.isUserInteractionEnabled = false
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

public extension View {

func statusBarStyle(
_ statusBarStyle: UIStatusBarStyle,
animationDuration: TimeInterval = 0.3
) -> some View {
modifier(StatusBarStyle(statusBarStyle: statusBarStyle, animationDuration: animationDuration))
func preferredStatusBarStyle(_ style: UIStatusBarStyle) -> some View {
modifier(StatusBarViewModifier(style: style))
}
}

public struct RootView<Content>: View where Content: View {

@Environment(\.scenePhase) var scenePhase
@State var interposed: Interposed = .pending
@ViewBuilder let content: () -> Content
var interposeLock = NSLock()

public var body: some View {
content()
.environment(\.interposed, interposed)
.onChange(of: scenePhase) { phase in
if case .active = phase {
interposeLock.lock()
if case .pending = interposed,
case true = UIApplication.shared.keyWindow?.rootViewController?.interpose() {
interposed = .successful
} else {
interposed = .failed
}
interposeLock.unlock()
}
}
}
extension UIApplication {

public init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
private static let statusBarWindowTag = -9999

fileprivate var statusBarWindow: UIWindow? {
guard let windowScene = connectedScenes.first as? UIWindowScene else {
return nil
}

if let window = windowScene.windows.first(where: { $0.tag == Self.statusBarWindowTag }) {
return window
} else {
let window = UIWindow(windowScene: windowScene)
window.windowLevel = .statusBar
window.tag = Self.statusBarWindowTag
window.isUserInteractionEnabled = false
window.rootViewController = StatusBarViewController()
window.isHidden = false
return window
}
}
}

0 comments on commit 9da79b2

Please sign in to comment.