Skip to content

Commit

Permalink
feat: implement overlay and background color in iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
andredestro committed Aug 21, 2024
1 parent f2a5a9f commit 673b25e
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Capacitor

extension CAPBridgeViewController {

open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.post(Notification(name: .capacitorViewDidAppear))
}

open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
NotificationCenter.default.post(Notification(name: .capacitorViewWillTransition))
}
}
6 changes: 6 additions & 0 deletions status-bar/ios/Sources/StatusBarPlugin/CAPNotifications.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Capacitor

extension Notification.Name {
public static let capacitorViewDidAppear = Notification.Name(rawValue: "CapacitorViewDidAppear")
public static let capacitorViewWillTransition = Notification.Name(rawValue: "CapacitorViewWillTransition")
}
175 changes: 175 additions & 0 deletions status-bar/ios/Sources/StatusBarPlugin/StatusBar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import Foundation
import Capacitor

public extension Notification.Name {
static let statusBarVisibilityChanged = Notification.Name("statusBarVisibilityChanged")
static let statusBarOverlayChanged = Notification.Name("statusBarOverlayChanged")
}

@objc public class StatusBar: NSObject {

private var bridge: CAPBridgeProtocol
private var isOverlayingWebview = true
private var backgroundColor = UIColor.black
private var backgroundView: UIView?
private var observers: [NSObjectProtocol] = []

init(bridge: CAPBridgeProtocol, config: StatusBarConfig) {
self.bridge = bridge
super.init()
setupObservers(with: config)
}

deinit {
observers.forEach { NotificationCenter.default.removeObserver($0) }
}

private func setupObservers(with config: StatusBarConfig) {
observers.append(NotificationCenter.default.addObserver(forName: .capacitorViewDidAppear, object: .none, queue: .none) { [weak self] _ in
self?.handleViewDidAppear(config: config)
})
observers.append(NotificationCenter.default.addObserver(forName: .capacitorStatusBarTapped, object: .none, queue: .none) { [weak self] _ in
self?.bridge.triggerJSEvent(eventName: "statusTap", target: "window")
})
observers.append(NotificationCenter.default.addObserver(forName: .capacitorViewWillTransition, object: .none, queue: .none) { [weak self] _ in
self?.handleViewWillTransition()
})
}

private func handleViewDidAppear(config: StatusBarConfig) {
setStyle(config.style)
setBackgroundColor(config.backgroundColor)
setOverlaysWebView(config.overlaysWebView)
}

private func handleViewWillTransition() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.resizeStatusBarBackgroundView()
self?.resizeWebView()
}
}

func setStyle(_ style: UIStatusBarStyle) {
bridge.statusBarStyle = style
}

func setBackgroundColor(_ color : UIColor) {
backgroundColor = color
backgroundView?.backgroundColor = color
}

func setAnimation(_ animation: String) {
if animation == "SLIDE" {
bridge.statusBarAnimation = .slide
} else if animation == "NONE" {
bridge.statusBarAnimation = .none
} else {
bridge.statusBarAnimation = .fade
}
}

func hide(animation: String) {
setAnimation(animation)
if bridge.statusBarVisible {
bridge.statusBarVisible = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.resizeWebView()
self?.backgroundView?.removeFromSuperview()
self?.backgroundView?.isHidden = true
}
NotificationCenter.default.post(name: .statusBarVisibilityChanged, object: getInfo())
}
}

func show(animation: String) {
setAnimation(animation)
if !bridge.statusBarVisible {
bridge.statusBarVisible = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [self] in
resizeWebView()
if !isOverlayingWebview {
resizeStatusBarBackgroundView()
bridge.webView?.superview?.addSubview(backgroundView!)
}
backgroundView?.isHidden = false
}
NotificationCenter.default.post(name: .statusBarVisibilityChanged, object: getInfo())
}
}

func getInfo() -> StatusBarInfo {
let style: String
switch bridge.statusBarStyle {
case .default:
style = "DEFAULT"
case .lightContent:
style = "DARK"
case .darkContent:
style = "LIGHT"
@unknown default:
style = "DEFAULT"
}

return StatusBarInfo(
overlays: isOverlayingWebview,
visible: bridge.statusBarVisible,
style: style,
color: UIColor.capacitor.hex(fromColor: backgroundColor),
height: getStatusBarFrame().size.height
)
}

func setOverlaysWebView(_ overlay: Bool) {
if overlay == isOverlayingWebview { return }
isOverlayingWebview = overlay
if overlay {
backgroundView?.removeFromSuperview()
} else {
initializeBackgroundViewIfNeeded()
bridge.webView?.superview?.addSubview(backgroundView!)
}
resizeWebView()
NotificationCenter.default.post(name: .statusBarOverlayChanged, object: getInfo())
}

private func resizeWebView() {
guard
let webView = bridge.webView,
let bounds = bridge.viewController?.view.window?.windowScene?.screen.bounds
else { return }
bridge.viewController?.view.frame = bounds
webView.frame = bounds
let statusBarHeight = getStatusBarFrame().size.height;
var webViewFrame = webView.frame;

if isOverlayingWebview {
let safeAreaTop = webView.safeAreaInsets.top;
if (statusBarHeight >= safeAreaTop && safeAreaTop > 0) {
webViewFrame.origin.y = safeAreaTop == 40 ? 20 : statusBarHeight - safeAreaTop
} else {
webViewFrame.origin.y = 0
}
} else {
webViewFrame.origin.y = statusBarHeight;
}
webViewFrame.size.height -= webViewFrame.origin.y
webView.frame = webViewFrame
}

private func resizeStatusBarBackgroundView() {
backgroundView?.frame = getStatusBarFrame()
}

private func getStatusBarFrame() -> CGRect {
return UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.statusBarManager?.statusBarFrame ?? .zero
}

private func initializeBackgroundViewIfNeeded() {
if backgroundView == nil {
backgroundView = UIView(frame: getStatusBarFrame())
backgroundView!.backgroundColor = backgroundColor
backgroundView!.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
backgroundView!.isHidden = !bridge.statusBarVisible
}
}
}
7 changes: 7 additions & 0 deletions status-bar/ios/Sources/StatusBarPlugin/StatusBarConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import UIKit

public struct StatusBarConfig {
var overlaysWebView = true
var backgroundColor: UIColor = .black
var style: UIStatusBarStyle = .default
}
9 changes: 9 additions & 0 deletions status-bar/ios/Sources/StatusBarPlugin/StatusBarInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

public struct StatusBarInfo {
public var overlays: Bool?
public var visible: Bool?
public var style: String?
public var color: String?
public var height: CGFloat?
}
113 changes: 57 additions & 56 deletions status-bar/ios/Sources/StatusBarPlugin/StatusBarPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,92 +15,93 @@ public class StatusBarPlugin: CAPPlugin, CAPBridgedPlugin {
CAPPluginMethod(name: "show", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "hide", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getInfo", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setOverlaysWebView", returnType: CAPPluginReturnPromise)
CAPPluginMethod(name: "setOverlaysWebView", returnType: CAPPluginReturnPromise),
]
private var observer: NSObjectProtocol?

private var statusBar: StatusBar?
override public func load() {
observer = NotificationCenter.default.addObserver(forName: Notification.Name.capacitorStatusBarTapped, object: .none, queue: .none) { [weak self] _ in
self?.bridge?.triggerJSEvent(eventName: "statusTap", target: "window")
guard let bridge = bridge else { return }
statusBar = StatusBar(bridge: bridge, config: statusBarConfig())
}

private func statusBarConfig() -> StatusBarConfig {
var config = StatusBarConfig()
config.overlaysWebView = getConfig().getBoolean("StatusBarOverlaysWebView", config.overlaysWebView)
if let colorConfig = getConfig().getString("StatusBarBackgroundColor"), let color = UIColor.capacitor.color(fromHex: colorConfig)
{
config.backgroundColor = color
}
if let configStyle = getConfig().getString("StatusBarStyle") {
config.style = style(fromString: configStyle)
}
return config
}

deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)

private func style(fromString: String) -> UIStatusBarStyle {
switch fromString.lowercased() {
case "dark", "lightcontent":
return .lightContent
case "light", "darkcontent":
return .darkContent
case "default":
return .default
default:
return .default
}
}

@objc func setStyle(_ call: CAPPluginCall) {
let options = call.options!

if let style = options["style"] as? String {
if style == "DARK" {
bridge?.statusBarStyle = .lightContent
} else if style == "LIGHT" {
bridge?.statusBarStyle = .darkContent
} else if style == "DEFAULT" {
bridge?.statusBarStyle = .default
}
if let styleString = options["style"] as? String {
statusBar?.setStyle(style(fromString: styleString))
}

call.resolve([:])
}

@objc func setBackgroundColor(_ call: CAPPluginCall) {
call.unimplemented()
}

func setAnimation(_ call: CAPPluginCall) {
let animation = call.getString("animation", "FADE")
if animation == "SLIDE" {
bridge?.statusBarAnimation = .slide
} else if animation == "NONE" {
bridge?.statusBarAnimation = .none
} else {
bridge?.statusBarAnimation = .fade
guard
let hexString = call.options["color"] as? String,
let color = UIColor.capacitor.color(fromHex: hexString)
else { return }
DispatchQueue.main.async { [weak self] in
self?.statusBar?.setBackgroundColor(color)
}
call.resolve()
}

@objc func hide(_ call: CAPPluginCall) {
setAnimation(call)
bridge?.statusBarVisible = false
let animation = call.getString("animation", "FADE")
DispatchQueue.main.async { [weak self] in
self?.statusBar?.hide(animation: animation)
}
call.resolve()
}

@objc func show(_ call: CAPPluginCall) {
setAnimation(call)
bridge?.statusBarVisible = true
let animation = call.getString("animation", "FADE")
DispatchQueue.main.async { [weak self] in
self?.statusBar?.show(animation: animation)
}
call.resolve()
}

@objc func getInfo(_ call: CAPPluginCall) {
DispatchQueue.main.async { [weak self] in
guard let bridge = self?.bridge else {
return
}
let style: String
switch bridge.statusBarStyle {
case .default:
if bridge.userInterfaceStyle == UIUserInterfaceStyle.dark {
style = "DARK"
} else {
style = "LIGHT"
}
case .lightContent:
style = "DARK"
default:
style = "LIGHT"
}

guard let info = self?.statusBar?.getInfo() else { return }
call.resolve([
"visible": bridge.statusBarVisible,
"style": style
"visible": info.visible!,
"style": info.style!,
"color": info.color!,
"overlays": info.overlays!
])
}
}

@objc func setOverlaysWebView(_ call: CAPPluginCall) {
call.unimplemented()
guard let overlay = call.options["overlay"] as? Bool else { return }
DispatchQueue.main.async { [weak self] in
self?.statusBar?.setOverlaysWebView(overlay)
}
call.resolve()
}
}
Loading

0 comments on commit 673b25e

Please sign in to comment.