I have an base view controller and all my viewcontrollers inherit it.
#interface BaseViewController () <UIGestureRecognizerDelegate>
#end
#implementation BaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.tabBarController.tabBar.translucent = NO;
self.navigationController.navigationBar.translucent = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
self.view.backgroundColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.backButton];
self.navigationItem.backBarButtonItem.title = #"";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault;
}
- (void) popToPreViewController {
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - getter and setter
- (UIButton *) backButton {
if (!_backButton) {
_backButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 20.0, 20.0)];
[_backButton addTarget:self action:#selector(popToPreViewController) forControlEvents:UIControlEventTouchUpInside];
[_backButton setImage:[UIImage imageNamed:#"main_back"] forState:UIControlStateNormal];
_backButton.hidden = YES;
}
return _backButton;
}
#end
Sometimes push to another view controller will get stuck ,but the app does not crash.Press home button and open the app again, it shows the another view controller . Is there something wrong with this baseviewcontroller ?
I have been troubled by this problem for a long time, now find a solution.
The reason for this problem is that the root controller performs a gesture pop operation, the problem described above occurs. The workaround is to disable the gesture pop at the root controller and open the gesture pop on the non root controller.
/// The controller is fully displayed, opening the gesture pop
Func navigationController (_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
If viewControllers.first == viewController {
InteractivePopGestureRecognizer? .isEnabled = false
} Else {
InteractivePopGestureRecognizer? .isEnabled = true
}
NavigationBar.sendSubview (toBack: navigationBar.overlay)
}
Related
I'd like to know when BarButton items gets the accessibility focus or loses the accessibility focus and so I implemented the informal protocol methods of UIAccessibilityFocus but it's not still firing.
extension UIBarButtonItem {
override open func accessibilityElementDidBecomeFocused() {
if self.accessibilityElementIsFocused() {
print("My element has become focused.")
}
}
override open func accessibilityElementDidLoseFocus() {
if self.accessibilityElementIsFocused() {
print("My element has lost focus.")
}
}
override open func accessibilityElementIsFocused() -> Bool {
if (self.accessibilityIdentifier == "hamburger") {
return true
} else {
return false
}
}
I imported the swift file into viewcontroller as well
#import "Sample-Swift.h"
then I tried subclassing and implemented the methods that also didn't work
.h header file
#import <UIKit/UIKit.h>
#import <UIKit/UIAccessibility.h>
NS_ASSUME_NONNULL_BEGIN
#interface HamburgerButton : UIBarButtonItem
#end
NS_ASSUME_NONNULL_END
.m implementation file
#implementation HamburgerButton
- (BOOL)isAccessibilityElement
{
return YES;
}
- (void)accessibilityElementDidBecomeFocused {
if ([self accessibilityElementIsFocused]) {
NSLog(#"My element has become focused.");
}
}
- (void)accessibilityElementDidLoseFocus {
if ([self accessibilityElementIsFocused]) {
NSLog(#"My element has lost focus.");
}
}
- (BOOL)accessibilityElementIsFocused {
if ([self.accessibilityIdentifier isEqualToString:#"hamburger"]) {
return YES;
} else {
return NO;
}
}
#end
Here is the implementation in view controller
HamburgerButton *leftButton = [[HamburgerButton alloc]
initWithTitle:#"Hamburger"
style:UIBarButtonItemStylePlain
target:self
action:#selector(flipView:)];
leftButton.accessibilityIdentifier=#"Hamburger";
leftButton.tag = 88;
leftButton.isAccessibilityElement = YES;
HamburgerButton *rightButton = [[HamburgerButton alloc]
initWithTitle:#"Chat"
style:UIBarButtonItemStylePlain
target:self
action:#selector(flipView:)];
rightButton.accessibilityIdentifier=#"Chat";
rightButton.tag = 89;
rightButton.isAccessibilityElement = YES;
self.navigationItem.leftBarButtonItem = leftButton;
self.navigationItem.rightBarButtonItem = rightButton;
Even though the focus comes and goes away from bar button , I'm not getting the call back on accessibilityElementDidBecomeFocused
Any ideas what could be done to get accessibilityElementDidBecomeFocused firing ?
Update 1:
I could achieve this functionality by the means of notification observer but it's not giving enough information about the receiver of focus so couldn't differentiate one bar button from the other.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(hamburgerGotFocus:) name:UIAccessibilityElementFocusedNotification object:nil];
and find the selector method below
-(void)hamburgerGotFocus:(NSNotification *) notification{
NSLog(#"Focus:%#",notification);
UIView *receiver = notification.userInfo[#"UIAccessibilityFocusedElementKey"];
if(receiver!=nil){
NSString *strElement = [[NSString alloc]initWithFormat:#"%#",notification.userInfo[#"UIAccessibilityFocusedElementKey"]];
if([strElement containsString:#"UIButtonBarButton"]){
}
}
}
Here is the log of notification
2022-10-12 18:57:03.992859+0530 Sample[32427:1579550] Focus:NSConcreteNotification 0x280ac9980 {name = UIAccessibilityElementFocusedNotification; userInfo = {
UIAccessibilityAssistiveTechnologyKey = UIAccessibilityNotificationVoiceOverIdentifier;
UIAccessibilityFocusedElementKey = "<_UIButtonBarButton: 0x10690fce0; frame = (0 0; 49 44); tintColor = UIExtendedSRGBColorSpace 0 1 0 1; gestureRecognizers = <NSArray: 0x2804262e0>; layer = <CALayer: 0x280afa9e0>>";
}}
Update 2:
I tried doing this with UIlabel using Category and subclassing both worked
#interface SampleLabel : UILabel
#end
#implementation SampleLabel
- (void)accessibilityElementDidBecomeFocused {
NSLog(#"accessibilityIdentifier:%#",self.accessibilityIdentifier);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, #"sample label from subclass");
}
- (void)accessibilityElementDidLoseFocus {
if ([self accessibilityElementIsFocused]) {
NSLog(#"My element has lost focus.subclass");
}
}
- (BOOL)accessibilityElementIsFocused {
return YES;
}
#end
By means of category
#interface UILabel (SampleLabel1)
#end
#implementation UILabel (SampleLabel1)
- (void)accessibilityElementDidBecomeFocused {
NSLog(#"accessibilityIdentifier:%#",self.accessibilityIdentifier);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, #"sample label from category");
}
- (void)accessibilityElementDidLoseFocus {
if ([self accessibilityElementIsFocused]) {
NSLog(#"My element has lost focus.Category");
}
}
- (BOOL)accessibilityElementIsFocused {
return YES;
}
#end
I'm wondering whether accessibilityElementDidBecomeFocused is not compatible with UIBarButtonItem ?
FYI:
I'm following this tutorial to implement accessibilityElementDidLoseFocus.
accessibilityElementDidBecomeFocused
is not firing for bar button but it's firing for UIButton. So I just created a bar button using UIButton like below and the problem is fixed
// Image
UIImage *img1 = [UIImage imageNamed:#"img1"];
// Button
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
[btn1 setImage:img1 forState:UIControlStateNormal];
btn1.frame = CGRectMake(0.0, 0.0, img1.size.width, img1.size.height);
btn1.tintColor = [UIColor greenColor];
[btn1 addTarget:self action:#selector(barButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
// Bar button
UIBarButtonItem *barButton1 = [[UIBarButtonItem alloc]initWithCustomView:btn1];
// Accessibility element
btn1.isAccessibilityElement = true;
img1.isAccessibilityElement = false;
btn1.accessibilityLabel = #"some accessibility text";
btn1.accessibilityTraits = UIAccessibilityTraitNone;
self.navigationItem.rightBarButtonItem = barButton1;
i have a problem with navigation bar title. i have 2 screen, when i click back button on screen 2 it will back to screen 1 but the title of screen 1 is disappear. Here is my snippet code
screen1 .m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = #"title 1";
....
screen 2 .m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = self.stringTitle;
// change the back button and add an event handler
self.navigationItem.hidesBackButton = YES;
//set custom image to button if needed
UIImage *backButtonImage = [UIImage imageNamed:#"btn_back.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:backButtonImage forState:UIControlStateNormal];
button.frame = CGRectMake(-12, 2, backButtonImage.size.width, backButtonImage.size.height);
[button addTarget:self action:#selector(backAction) forControlEvents:UIControlEventTouchUpInside];
UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, backButtonImage.size.width-15, backButtonImage.size.height)];
[backButtonView addSubview:button];
UIBarButtonItem *customBarItem = [[UIBarButtonItem alloc] initWithCustomView:backButtonView];
self.navigationItem.leftBarButtonItem = customBarItem;
self.navigationController.navigationBar.titleTextAttributes = #{NSForegroundColorAttributeName: [UIColor whiteColor]};
....
}
- (void) backAction
{
[self.navigationController popViewControllerAnimated:YES];
}
i've tried several tricks to make title of screen 1 appears with the following tricks:
set title on viewwillappear method
- (void)viewWillAppear:(BOOL)animated{self.title = #"tes";}
set title on viewdidappear method
- (void)viewDidAppear:(BOOL)animated{
self.navigationController.navigationBar.topItem.title = #"YourTitle";
self.title = #"tes";
}
but the title is still disappears. please help
here is the way how i moving to next screen
- (IBAction)btGenerateTokenAction:(id)sender {
SofttokenResultController *controller = [[SofttokenResultController alloc] initWithNibName:#"SofttokenResultController" bundle:nil];
controller.navigationController.navigationBar.backItem.title = #"Kembali";
[ViewLoadingUtil animateToNextView:controller from:self :#""];
}
+ (void) animateToNextView:(UIViewController *)controller from:(UIViewController *)fromController :(NSString *)navTitle{
NSLog(#"animateToNextView called");
[fromController.navigationController pushViewController:controller animated:YES];
}
This solved my problem:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated: animated)
self.navigationItem.title="Title text"
}
In my case, it was the backItem?.title that was the problem. In both my view controller and within a custom subclass of UINavigationController I was setting it to an empty string:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
...
navigationController.navigationBar.backItem?.title = ""
}
My guess would be that when using popViewController(animated:) the title is taken from backItem?.title and set into the main title attribute.
In viewWillAppear implement this:
self.navigationItem.title="Title text";
it usually happen when you set backItem?.title = "" because the title of the back controller is taken from this property
I face this issue from months and solved it just by commenting this line
Working for me solution. You can try this one:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.topItem?.title = ""
self.navigationItem.title = "Title"
}
Objective c solution:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear: animated];
self.navigationItem.title = #"Title text";
}
I come from the Swift world. However I tried many solutions. These were caused by the animation. If you change the Bool to false, it will solve your problem. Finally you can put your code in the viewWillAppear function.
override func viewWillAppear(animated: Bool) {
if animated {
//...
}
}
I write the codes below in my UIViewController that uses UINavigationController.
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>) self;
}
I build and run my app,
self.navigationItem.hidesBackButton = YES;
above that works correctly, but
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>) self;
that one DO NOT works.
So, I re-write the code below.
- (void)viewDidLoad {
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithCustomView:[[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 44.0f, 44.0f)]];
backBarButton.tintColor = [UIColor clearColor];
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
self.navigationItem.leftBarButtonItem = backBarButton;
}
It works correctly.
However, I want to use the first example.
The first one clearly express what I want to do.
Does someone have any idea?
In viewDidLoad, the view controller is not yet contained in a navigation controller, so the navigationController property is nil, which is why that line has no effect.
That said, assigning the delegate of UINavigationController's interactivePopGestureRecognizer is not good practice (I'm pretty sure it expects to be assigned to the navigation controller). Try disabling the gesture recognizer in viewWillAppear: instead:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
I have an iOS 7 app where I am setting a custom back button like this:
UIImage *backButtonImage = [UIImage imageNamed:#"back-button"];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[backButton setImage:backButtonImage forState:UIControlStateNormal];
backButton.frame = CGRectMake(0, 0, 20, 20);
[backButton addTarget:self
action:#selector(popViewController)
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backBarButtonItem;
But this disables the iOS 7 "swipe left to right" gesture to navigate to the previous controller. Does anyone know how I can set a custom button and still keep this gesture enabled?
EDIT:
I tried to set the viewController.navigationItem.backBarButtonItem instead, but this doesn't seem to show my custom image.
IMPORTANT:
This is a hack. I would recommend taking a look at this answer.
Calling the following line after assigning the leftBarButtonItem worked for me:
self.navigationController.interactivePopGestureRecognizer.delegate = self;
Edit:
This does not work if called in init methods. It should be called in viewDidLoad or similar methods.
Use the backIndicatorImage and backIndicatorTransitionMaskImage properties of the UINavigationBar if at all possible. Setting these on an a UIAppearanceProxy can easily modify behavior across your application. The wrinkle is that you can only set those on ios 7, but that works out because you can only use the pop gesture on ios 7 anyway. Your normal ios 6 styling can remain intact.
UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:#selector(setBackIndicatorImage:)])
{
appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:#"back"];
appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
//sets back button color
appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
//do ios 6 customization
}
Trying to manipulate the interactivePopGestureRecognizer's delegate will lead to a lot of issues.
I saw this solution http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ which subclasses UINavigationController. Its a better solution as it handles the case where you swipe before the controller is in place - which causes a crash.
In addition to this I noticed if you do a swipe on the root view controller (after pushing on one, and back again) the UI becomes unresponsive (also same problem in answer above).
So the code in the subclassed UINavigationController should look like so:
#implementation NavigationController
- (void)viewDidLoad {
[super viewDidLoad];
__weak NavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Hijack the push method to disable the gesture
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.enabled = NO;
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
// Enable the gesture again once the new controller is shown
self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}
#end
I use
[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:#"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:#"nav_back.png"]];
[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
Here is swift3 version of Nick H247's answer
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.isEnabled = false
}
super.pushViewController(viewController, animated: animated)
}
}
extension NavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
}
}
extension NavigationController: UIGestureRecognizerDelegate {}
I also hide the back button, replacing it with a custom leftBarItem.
Removing interactivePopGestureRecognizer delegate after push action worked for me:
[self.navigationController pushViewController:vcToPush animated:YES];
// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
This is from http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks, but it causes several bugs:
Push another viewController into the navigationController when swiping in from the left edge of the screen;
Or, swipe in from the left edge of the screen when the topViewController is popping up from the navigationController;
e.g. When the rootViewController of navigationController is showing, swipe in from the left edge of the screen, and tap something(QUICKLY) to push anotherViewController into the navigationController, then
The rootViewController does not respond any touch event;
The anotherViewController will not be shown;
Swipe from the edge of the screen again, the anotherViewController will be shown;
Tap the custom back button to pop the anotherViewController, crash!
So you must implement UIGestureRecognizerDelegate method in self.navigationController.interactivePopGestureRecognizer.delegate like this:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
}
return YES;
}
Try self.navigationController.interactivePopGestureRecognizer.enabled = YES;
I did not write this, but the following blog helped a lot and solved my issues with custom navigation button:
http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/
In summary, he implements a custom UINavigationController that uses the pop gesture delegate. Very clean and portable!
Code:
#interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
#end
#implementation CBNavigationController
- (void)viewDidLoad
{
__weak CBNavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
// Hijack the push method to disable the gesture
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = NO;
[super pushViewController:viewController animated:animated];
}
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
// Enable the gesture again once the new controller is shown
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = YES;
}
Edit. Added fix for problems when a user tries to swipe left on a root view controller:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] &&
self.topViewController == [self.viewControllers firstObject] &&
gestureRecognizer == self.interactivePopGestureRecognizer) {
return NO;
}
return YES;
}
RootView
override func viewDidAppear(_ animated: Bool) {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
ChildView
override func viewDidLoad() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension ChildViewController: UIGestureRecognizerDelegate {}
Use this logic to keep enable or disable the swipe gesture..
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
if (self.navigationController.viewControllers.count > 1)
{
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
else
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
}
I had a similar problem where I was assigning the current view controller as the delegate for the interactive pop gesture, but would break the gesture on any views pushed, or views underneath the view in the nav stack. The way I solved this was to set the delegate in -viewDidAppear, then set it to nil in -viewWillDisappear. That allowed my other views to work correctly.
Imagine we are using Apple's default master/detail project template, where master is a table view controller and tapping on it will show the detail view controller.
We want to customize the back button that appears in the detail view controller. This is how to customize the image, image color, text, text color, and font of the back button.
To change the image, image color, text color, or font globally, place the following in a location that is called before any of your view controllers are created (e.g. application:didFinishLaunchingWithOptions: is a good place).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];
// change the back button, using default tint color
navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:#"back"];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the back button, using the color inside the original image
navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:#"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the tint color of everything in a navigation bar
navigationBarAppearance.tintColor = [UIColor greenColor];
// change the font in all toolbar buttons
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
return YES;
}
Note, you can use appearanceWhenContainedIn: to have more control over which view controllers are affected by these changes, but keep in mind that you can't pass [DetailViewController class], because it is contained inside a UINavigationController, not your DetailViewController. This means you will need to subclass UINavigationController if you want more control over what is affected.
To customize the text or the font/color of a specific back button item, you must do so in the MasterViewController (not the DetailViewController!). This seems unintuitive because the button appears on the DetailViewController. However once you understand that the way to customize it is by setting a property on a navigationItem, it begins to make more sense.
- (void)viewDidLoad { // MASTER view controller
[super viewDidLoad];
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:#"Testing"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = buttonItem;
}
Note: attempting to set the titleTextAttributes after setting self.navigationItem.backBarButtonItem doesn't seem to work, so they must be set before you assign the value to this property.
Create a class 'TTNavigationViewController' which is subclass of 'UINavigationController' and make your existing navigation controller of this class either in storyboard/class, Example code in class -
class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationBarHidden(true, animated: false)
// enable slide-back
if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}}
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;