I'm trying to get the updated status bar frame height using didChangeStatusBarFrame, but…
The new/updated frame isn't up-to-date and sometimes returns the old and sometimes the new (correct) height.
func application(_ application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) {
print(UIApplication.shared.statusBarFrame.height) // Sometimes wrong
}
My current workaround is to use DispatchQueue.main.async:
func application(_ application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) {
DispatchQueue.main.async {
print(UIApplication.shared.statusBarFrame.height) // So far correct in all my tests
}
}
I guess it's because some layout is happening in the background and the statusBarFrame needs to update first.
What's the real reason and is there a better, not-so-hacky way to wait for the correct status bar frame height?
Related
I am using this Github-Repo for a reavealing splash view.
I am calling it like this inside my AppDelegate:
let revealingSplashView = RevealingSplashView(iconImage: UIImage(named: "wIcon")!, iconInitialSize: CGSize(width: 120 * UIScreen.main.bounds.width / 414.0, height: 120 * UIScreen.main.bounds.width / 414.0), backgroundColor: .white)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
revealingSplashView.startAnimation()
window?.rootViewController?.view.addSubview(revealingSplashView)
return true
}
I am facing an issue where sometimes after the animation is finished, it is not instantly transitioning to the other ViewController but showing a blank white screen.
Here is a screen-video for a better understanding.
Does anyone have an idea what can cause that?
Or is there an easy way to implement it without this repo?
You calling the revealingSplashView inside AppDelegate didFinishLaunchingWithOptions. If you check the documentation they clearly state to call it in the viewDidLoad method of your viewController. Your view has not been loaded that's why you got the delay.
Check the documentation which you linked above:
import RevealingSplashView
override func viewDidLoad() {
super.viewDidLoad()
//Initialize a revealing Splash with with the iconImage, the initial size and the background color
let revealingSplashView = RevealingSplashView(iconImage: UIImage(named: "twitterLogo")!,iconInitialSize: CGSize(width: 70, height: 70), backgroundColor: UIColor(red:0.11, green:0.56, blue:0.95, alpha:1.0))
//Adds the revealing splash view as a sub view
self.view.addSubview(revealingSplashView)
I'm trying to add a cover screen to app when app is pushed to background by user.
I have these two methods on AppDelegate to add/remove the cover view.
func showCover(){
let blackV = UIView(frame: UIApplication.shared.keyWindow?.bounds ?? .zero)
blackV.backgroundColor = .black
blackV.tag = kCoverTag
let imgV = UIImageView(image: UIImage(named: "Logo-White"))
imgV.center = blackV.center
blackV.addSubview(imgV)
UIApplication.shared.keyWindow?.addSubview(blackV)
}
func hideCover(){
if let blackV = UIApplication.shared.keyWindow?.subviews.filter({$0.tag == kCoverTag}).first{
blackV.removeFromSuperview()
}
}
The tag value is declared on AppDelegate:
let kCoverTag = 987
In the applicationWillResignActive , I apply the cover
func applicationWillResignActive(_ application: UIApplication) {
showCover()
}
and in didBecomeActive, I remove the cover.
func applicationDidBecomeActive(_ application: UIApplication) {
hideCover()
}
This works fine when app is sent to background by pressing home button.
Right after the applicationDidBecomeActive is called, when I print the subviews of keyWindow , I get the output as:
po UIApplication.shared.keyWindow?.subviews
▿ Optional<Array<UIView>>
▿ some : 2 elements
- 0 : <UITransitionView: 0x7f97bd82ffc0; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x6000028ecce0>>
- 1 : <UIView: 0x7f97bae27f20; frame = (0 0; 414 896); tag = 987; layer = <CALayer: 0x600002868400>>
This is good as it shows the cover view as one of the subview added to the key window.
Now, when the app is sent to background by calling UIApplication.shared.openURL , the applicationWillResignActive is called as expected.
And when the user press the "Back to app button" , the app will be shown back to user.
Now,when I print the subviews of key window in the didBecomeActive method right before calling hideCover() method, it looks like it has only one subview.
po UIApplication.shared.keyWindow?.subviews
▿ Optional<Array<UIView>>
▿ some : 1 element
- 0 : <UITransitionView: 0x7f97bfc05510; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x6000029984c0>>
Since this shows only one view, the hideCover() method couldn't find the subview and hence couldn't remove it. But in the app I could see the cover view .
I'm wondering why the keyWindow subviews didn't show the added cover view.
I'm testing this in simulator.
Update:
I did two separate print calls
Right after adding the cover view:
print("resign active",UIApplication.shared.keyWindow?.subviews)
This show two subviews for keyWindow
and right before removing the cover view in didBecomeActive method:
print("did become active",UIApplication.shared.keyWindow?.subviews)
This show only ONE subview for keyWindow
Don't use keyWindow here. That's not the right tool. There can be other windows created by the system and may have input focus via makeKey(). Present the view from the window.rootViewController from your UIApplicationDelegate. See QA1838: Preventing Sensitive Information From Appearing In The Task Switcher for Apple's recommended way to implement what you're trying to do. Also see Preparing Your UI to Run in the Background for additional guidance.
I use this to cover my app
var secureView: UIView?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
secureView = UIView()
secureView?.backgroundColor = UIColor.white
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
public func applicationDidBecomeActive(_ application: UIApplication) {
self.secureView?.removeFromSuperview()
}
public func applicationWillResignActive(_ application: UIApplication) {
if let window = UIApplication.shared.windows.first, let view = secureView {
view.frame = window.bounds
window.addSubview(view)
}
}
for someone want to add story board:
override
func applicationWillResignActive(_ application: UIApplication) {
showSplashScreen()
}
override
func applicationDidBecomeActive(_ application: UIApplication) {
removeSplashScreen()
}
func showSplashScreen() {
let launchScreen = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
guard let launchScreen = launchScreen,
let launchView = launchScreen.view else {
return
}
launchView.tag = 8888
launchView.frame = window.bounds
self.window?.addSubview(launchView)
self.window?.makeKeyAndVisible()
}
func removeSplashScreen() {
guard let window = UIApplication.shared.windows.last,
let view = window.viewWithTag(8888) else {
return
}
view.removeFromSuperview()
}
I am experiencing a strange issue using IQKeyboardManager when keyboard split mode is enabled. Keyboard's background isn't transparent:
But keyboard's background is transparent when IQKeyboardManager is not used:
EDIT How to reproduce:
//AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
IQKeyboardManager.shared.enable = true
...
}
//ViewController.swift
override func viewDidLoad() {
...
textField.keyboardDistanceFromTextField = 140 // or any constant
...
}
Any suggestion? Thanks!
I got an answer from IQKeyboardManager library's maintainer (answer).
Keyboard's background is fully transparent if you disable autoToolbar:
IQKeyboardManager.shared.enableAutoToolbar = false
getting the statusBarFrame doesn't appear to return the correct value in iOS 11. I realize there may be a nicer way of dealing with this in the future using the Safe Area but right now I really need to get the statusBarFrame to work as it did before iOS 11. Here's how I usually get it.
UIApplication.shared.statusBarFrame
I'v verified that my app works properly before iOS 11. But in iOS 11 it appears to be backwards; for example, for an iPhone in portrait it should 20 and in landscape 0. But it returns 0 in portrait and 20 in landscape.
I've read all the posts regarding status bar issues and none address this problem.
I think it appears to be a timing issue. I wrapped the code that uses the statusBarFrame in a DispatchQueue.main.async {} closure and it works fine. I would consider this more of a bandaid. Still hoping for a proper solution.
Suggestions appreciated.
Thanks!
You can set background color for status bar during application launch or during viewDidLoad of your view controller. Here it works for me, in following ways.
extension UIApplication {
var statusBarView: UIView? {
return value(forKey: "statusBar") as? UIView
}
}
// Set upon application launch, if you've application based status bar
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UIApplication.shared.statusBarView?.backgroundColor = UIColor.red
return true
}
}
or
// Set it from your view controller if you've view controller based statusbar
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.shared.statusBarView?.backgroundColor = UIColor.red
}
}
Here is result:
Is using UIScreen.mainScreen().bounds.size.height to position or size elements in my layout based on device size an acceptable thing to do? Or will this kind of thing have me hauled off to a infinite loop cubical farm for a thrashing?
Yes it is acceptable, however you do not want to call UIScreen.mainScreen() more than once or twice, as it's a very slow function.
So keep a copy of the result cached like so:
var mainScreen: UIScreen
override init() {
self.mainScreen = UIScreen.mainScreen()
}
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
let height = self.mainScreen.bounds.size.height;
// do stuff with height
}