I am upgrading my code from iOS 8 to iOS 9. I have a code snippet in my program
[[UIApplication applicationName] setStatusBarHidden:YES];.
I am getting the warning "setStatusBarHidden is deprecated in iOS 9.0, Use -[UIViewController prefersStatusBarHidden". If I just replace 'setStatusBarHidden' with 'prefersStatusBarHidden', I get 'instance method not found'.
Can someone please suggest me how to solve this problem?
Add below code to your view controller..
- (BOOL)prefersStatusBarHidden {
return NO;
}
Note :
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate method.
For childViewController, To specify that a child view controller
should control preferred status bar hidden/unhidden state, implement
the childViewControllerForStatusBarHidden method.
prefersStatusBarHidden is available from iOS 7+.
Use this in Your UIViewController class
var isHidden = true{
didSet{
self.setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool {
return isHidden
}
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate() method. To specify that a child
view controller should control preferred status bar hidden/unhidden
state, implement the childViewControllerForStatusBarHidden method.
you have to add method in yourViewController.m
- (BOOL)prefersStatusBarHidden {
return NO;
}
Swift 3.1 Xcode 8.2.1
Change in info.plist the row View controller-based status bar appearance and set it to NO
In your target settings tick "Hide Status bar"
Both steps are required
Here is my swift code for setting status bar hidden and style.
extension UIViewController {
public var privateStatusBarHidden: Bool {
return statusBarHidden
}
public var privateStatusBarStyle: UIStatusBarStyle {
return statusBarStyle
}
public func setStatusBarHidden(hidden: Bool, animated: Bool = false) {
statusBarHidden = hidden
if animated {
UIView.animate(withDuration: 0.25, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
} else {
self.setNeedsStatusBarAppearanceUpdate()
}
}
public func setStatusBar(style: UIStatusBarStyle) {
statusBarStyle = style
self.setNeedsStatusBarAppearanceUpdate()
}
public static func swizzleStatusBarHiddenPropertyForViewController() {
var original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.prefersStatusBarHidden))
var changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarHidden))
method_exchangeImplementations(original, changeling)
original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.preferredStatusBarStyle))
changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarStyle))
method_exchangeImplementations(original, changeling)
original = class_getClassMethod(UIViewController.self, #selector(UIViewController.swizzleStatusBarHiddenPropertyForViewController))
changeling = class_getClassMethod(UIViewController.self, #selector(UIViewController.emptyFunction))
method_exchangeImplementations(original, changeling)
}
#objc private static func emptyFunction() {}
}
Usage
in lauching function
UIViewController.swizzleStatusBarHiddenPropertyForViewController()
for hide/show statusBar, in UIViewController
. self.setStatusBar(hidden: true/false)
Swift 3 with Xcode 8.3.3
1) Add a row in you Info.plist.
2) In your ViewController ViewDidLoad() override add:
UIApplication.shared.isStatusBarHidden = true
Related
Navigation rootViewController is dependent on user set up due to that I have tried to check if user did do all the onboarding forms before the main screen launch. My question is how to set inatial value to UserDefaults in my navigation. This always prints "false"
extension UserDefaults {
var didUserSetUp: Bool? {
get {
///// Register the app default:
/// Initialize value from UserDefaults returns false
UserDefaults.standard.register(defaults: ["didUserSetUp" : false])
return UserDefaults.standard.bool(forKey: "didUserSetUp")
}
set {
/// Set value to UserDefaults
UserDefaults.standard.set(newValue, forKey: "didUserSetUp")
}
}
}
The way is set it in code:
UserDefaults.standard.didUserSetUp = false
if UserDefaults.standard.didUserSetUp { }
You'd need to show example code of how you're attempting to use the property you've created, but I tried what you've shown here, and was able to get it working as expected. Here's what I did:
extension UserDefaults {
var didCompleteOnboarding: Bool? {
get {
UserDefaults.standard.register(defaults: ["didCompleteOnboarding": false])
return UserDefaults.standard.bool(forKey: "didCompleteOnboarding")
}
set {
UserDefaults.standard.set(newValue, forKey: "didCompleteOnboarding")
}
}
}
Then in the root view controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.didCompleteOnboarding = true
print(UserDefaults.standard.didCompleteOnboarding ?? false)
}
}
Prints true in the console.
I want to generate a menu which will call functions on a class other than that one which generated the menu, and then place that in Finder. However, when I click the menu item, the system error beep plays and my function is never called.
Here's a SSCCE:
import Cocoa
import FinderSync
#objc(FinderSync)
class FinderSync: FIFinderSync {
var observedFolder = URL(fileURLWithPath: "/")
override init() {
super.init()
NSLog("\(FinderSync.self.className()) launched from \(Bundle.main.bundlePath)")
// Set up the directory we are syncing.
FIFinderSyncController.default().directoryURLs = [self.observedFolder]
}
// MARK: - Menu and toolbar item support
override var toolbarItemName: String {
return "Wonderful Test App"
}
override var toolbarItemToolTip: String {
return "This is wonderful"
}
override var toolbarItemImage: NSImage {
return NSImage(named: NSImage.cautionName)!
}
override func menu(for menuKind: FIMenuKind) -> NSMenu {
let menu = NSMenu(title: "")
let menuItem = NSMenuItem(title: "Click me!", action: #selector(SomeOtherClass.remoteAction), keyEquivalent: "")
menuItem.target = SomeOtherClass.shared
menuItem.action = #selector(SomeOtherClass.remoteAction)
menu.addItem(menuItem)
return menu
}
}
#objc(SomeOtherClass)
public class SomeOtherClass: NSObject {
public static let shared = SomeOtherClass()
deinit {
NSLog("Deallocated!")
preconditionFailure("Shared instance should never be deallocated!")
}
#IBAction
#objc(remoteAction:)
public func remoteAction(_ sender: Any?) {
NSLog("Remote!")
}
}
I've verified via the memory debugger that SomeOtherClass.shared is still in memory before, during, and after the menu item is clicked, so it's not being deallocated or anything.
It appears you can't add actions which are within any class other than your FinderSync extension's principal class. Which is about as stupid as everything else about NSMenuItem, so I'm not surprised.
So, you'll have to move the action(s) into your FinderSync class, despite how ugly that might be for organization.
In my app I have a function where I want it to print something depending on the current view controller that is loaded. I do this by setting a global variable (Bool) and then toggling the flags in the view controller classes. From my main class I have something like this:
var FirstViewControllerisVisible: Bool = false
var SecondViewControllerisVisible: Bool = false
var ThirdViewControllerisVisible: Bool = false
#objc func PlayAgainfunc(_ sender: Any) {
if counter % 15 == 0 {
if FirstViewControllerisVisible == true {
print("First View Controller is visible")
} else if SecondViewControllerisVisible == true {
print("Second View Controller is visible")
} else if ThirdViewControllerisVisible == true {
print("Third View Controller is visible")
}
}
counter += 1
}
Then, in those classes I can set the flags like this:
override func viewDidAppear(_ animated: Bool) {
FirstViewControllerisVisible = true
}
override func viewDidDisappear(_ animated: Bool) {
FirstViewControllerisVisible = false
}
This worked great when it was exclusively Swift, but the problem with global variables is that they can't be accessed by Objective-C. I can't find a way to set flags in my ObjC classes and then check if true or false. For instance, If I tried:
- (void)viewDidAppear:(BOOL)animated; {
[(FirstViewControllerisVisible) == true];
}
- (void)viewDidDisappear:(BOOL)animated; {
[(FirstViewControllerisVisible) == false];
}
I would get the error 'use of undeclared identifier' because my global vars declared in Swift are Swift only.
After the view controller has loaded, you can
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
print(type(of: viewController))
}
add below mentioned extension. you will get the desired result.
extension UIApplication {
/// will return currently showing view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}
and access it like:
let viewController = UIApplication.topMostViewController
Happy Coding
My app's statusbar style is UIStatusBarStyleLightContent and it's set in my rootViewController as preferredStatusBarStyle.
Now I have a problem that when opening SFSafariViewController from within my app, it has inherited statusbar style that is light and invisible on the white background of SFSafariViewController.
Is there a way to set statusbar style for SFSafariViewController?
P.S. I tried to subclass SFSafariViewController and override this method but it doesn't help.
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault;
}
Update:
[[UIApplication sharedApplication] setStatusBarStyle:] does the trick, but this method is deprecated in iOS 9.
You don't have to subclass SFSafariViewController at all.
Just set modalPresentationCapturesStatusBarAppearance = true on your instance of SFSafariViewController and it will handle the rest on its own.
This works because its own default preferredStatusBarStyle is, you guessed it, .default. The view hierarchy is still relying on the presenting view controller for status bar appearance, so by setting modalPresentationCapturesStatusBarAppearance to true, it will be the receiver asked for status bar appearance.
TL;DR
safariViewController.modalPresentationCapturesStatusBarAppearance = true
(This behavior is overridden, doesn't work, when UIViewControllerBasedStatusBarAppearance is set to NO in your Info.plist)
It is not the best solution, but it works.
class MySafariViewContoller: SFSafariViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
UIApplication.sharedApplication().statusBarStyle = .Default
}
override func viewWillDisappear(animated: Bool) {
super.viewWillAppear(false)
UIApplication.sharedApplication().statusBarStyle = .LightContent
}
}
Change .Default and .LightContent as you prefer.
You can use extensions.
extension SFSafariViewController {
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
UIApplication.shared.statusBarStyle = .default
}
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
UIApplication.shared.statusBarStyle = .lightContent
}
}
How about that?
class MyCustomSafariViewController: SFSafariViewController {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
}
Or private API - if you can use it.
I know that theoretically it's possible to create multiple instances of the same class with a property that would have a different value for each instance.
The thing is, I can't make it happen.
Each time I'm creating a new instance, it gets the property's value of the other instances, and when I'm changing one value for an instance, it changes the other's too.
So my guess is that I'm doing something wrong (obviously), like accessing the class property value instead of the instance property value... Here's the code.
class CustomUIImageView: UIImageView {
var someParameter: Bool = false // This is the property I want to be different in each version of the instance.
}
class ClassSiege: UIViewController, UIGestureRecognizerDelegate {
var myView: CustomUIImageView! //the instance declaration.
// I use this gesture recognizer to find out the value of the instance I'm tapping on.
func handleTap (sender: UITapGestureRecognizer) {
print("value of someParameter \(self.myView.someParameter)")
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let iv: UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x // 'Blackboard' is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
if centerRondY - centerBoardY < 100 {
self.myView.someParameter = true // If the distance between myView and the blackboard is under 100 I want the instance's property to become true.
} else {
self.myView.someParameter = false // On the other hand, if the distance is greater than 100, I want it to be false.
}
}
// When the user pushes a button, it triggers this method that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
// some code...
// Here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}
}
So clearly that's not the good way to do it, but what's wrong, and how can it be solved?
Basing my answer on your related question.
If what you want to achieve is initializing a property with a value that you provide, just add a new parameter to the initializer. If for instance you are using the initializer with a CGRect passed in, then you can implement an initializer like this:
class CustomUIImageView : UIImageView {
let someParameter : Bool
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, someParameter: Bool) {
self.someParameter = someParameter
super.init(frame: frame)
}
}
I hope that this is what you are looking for - let me know otherwise.
I've found the solution, and if you've been facing the same issu, here's how to deal with it.
The secret is to downcast the recognizer.view to take the parameter of the subclass CustomUIImageView.
here's how :
func handleTap (sender: UITapGestureRecognizer) {
println("value of someParameter \(self.myView.someParameter)") //I use this gesture recognizer to find out the value of the instance I'm tapping on.
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let iv : UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x //blackboard is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
var myParameter = recognizer.view as CustomUIImageView //<- this is the key point. Downcasting let you access the custom subclass parameters of the object that is currently moved
if centerRondY - centerBoardY < 100 {
myParameter.someParameter = true //so now I'm really changing the parameter's value inside the object rather than changing a global var like I did before.
} else {
myParameter.someParameter = false
}
}
//when user pushes a button, it triggers this func that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
some code...
//here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}