I want to make a reddish translucent UINavigationBar (for iOS9), and I found that the following codes are essential:
navController.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
navController.navigationBar.shadowImage = UIImage()
navController.navigationBar.backgroundColor = UIColor(red: 1.0,green: 0.3,blue: 0.3,alpha: 0.9)
navController.navigationBar.translucent = true
It works fine if I simply created a UINavigationBar (instead of creating a UINavigationController), but it looks like this as I created a UINavigationController.
enter image description here
I found the culprit to be the line navController.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
, but if I remove it, the bar looks like this:
enter image description here
So how can I solve this problem? (I'm creating the UINavigationController programmatically and sorry for the inconvenience that I cannot imbed images)
Edit:
You need to subclass the UINavigationBar in order to get the translucent background of desired color. Have a look at this answer:
https://stackoverflow.com/a/19043174/2082569
Adding necessary code here too for reference:
#interface UnderlayNavigationBar : UINavigationBar
#end
#interface UnderlayNavigationBar ()
{
UIView* _underlayView;
}
- (UIView*) underlayView;
#end
.
#implementation UnderlayNavigationBar
- (void) didAddSubview:(UIView *)subview
{
[super didAddSubview:subview];
if(subview != _underlayView)
{
UIView* underlayView = self.underlayView;
[underlayView removeFromSuperview];
[self insertSubview:underlayView atIndex:1];
}
}
- (UIView*) underlayView
{
if(_underlayView == nil)
{
const CGFloat statusBarHeight = 20; // Make this dynamic in your own code...
const CGSize selfSize = self.frame.size;
_underlayView = [[UIView alloc] initWithFrame:CGRectMake(0, -statusBarHeight, selfSize.width, selfSize.height + statusBarHeight)];
[_underlayView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
[_underlayView setBackgroundColor:[UIColor colorWithRed:1.0f green:0.3f blue:0.3f alpha:1.0f]];
[_underlayView setAlpha:0.7f];
[_underlayView setUserInteractionEnabled:NO];
}
return _underlayView;
}
#end
.
UIViewController* rootViewController = ...;
UINavigationController* navigationController = [[UINavigationController alloc] initWithNavigationBarClass:[UnderlayNavigationBar class] toolbarClass:nil];
[navigationController setViewControllers:#[rootViewController]];
here's my code in mainViewController.m:
#import "SideViewController.h"
- (void)viewDidLoad{
SideViewController *sideVC = [[SideViewController alloc] initWithFrame:CGRectMake(0, 0, 250,self.view.bounds.size.height)];
[self.navigationController.view addSubview:sideVC.view];
}
mainViewController was embedded in UINavigationController via storyboard
sideViewController was added to UINavigationController as its subview because I needed it cover the Navigation Bar
and how it shows on iOS8 simulator:
How could I make it fit the screen bounds?
I guess autoresize mask solve this problem:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
sideVC = [SideViewController new];
[sideVC.view setFrame:CGRectMake(0, 0, 250,self.view.bounds.size.height)];
sideVC.view.autoresizingMask = UIViewAutoresizingNone;
}
return self;
}
I have a UINavigationController to which I need to add a second UINavigationBar. Neither of those bars is translucent. Problem is, view controllers that I put inside this navigation controller are partially covered by my second navigation bar. Where do I adjust the frames of those view controllers' views so that I don't get a "blinking" effect of them changing frames while being visible?
EDIT:
This is in viewDidLoad:
UINavigationBar *secondaryNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, 50)];
secondaryNavBar.translucent = NO;
if ([secondaryNavBar respondsToSelector:#selector(setBarTintColor:)]) { //it has to work on iOS 6 as well
secondaryNavBar.barTintColor = [UIColor darkGrayColor];
secondaryNavBar.tintColor = [UIColor whiteColor];
}
else {
secondaryNavBar.tintColor = [UIColor darkGrayColor];
}
[self.view addSubview:secondaryNavBar];
self.secondaryNavBar = secondaryNavBar;
Here's a working solution. Certainly not the best, and I did not make it to support iOS 6, you'll have to work on it and test it.
CustomNavigationController.m :
#implementation CustomNavigationController {
UINavigationBar *bottomNavBar;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self showNavBar];
}
- (void)showNavBar {
UINavigationBar *secondaryNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, 50)];
secondaryNavBar.translucent = NO;
if ([secondaryNavBar respondsToSelector:#selector(setBarTintColor:)]) { //it has to work on iOS 6 as well
secondaryNavBar.barTintColor = [UIColor darkGrayColor];
secondaryNavBar.tintColor = [UIColor whiteColor];
}
else {
secondaryNavBar.tintColor = [UIColor darkGrayColor];
}
[self.view addSubview:secondaryNavBar];
bottomNavBar = secondaryNavBar;
[self layoutNavBar];
}
- (void)layoutNavBar {
// Get the currently displayed view
UIView *contentView = self.topViewController.view;
// Get its frame and height
CGRect contentFrame = contentView.frame;
float height = contentFrame.size.height;
// Adapt height and y origin with the new nav bar
contentFrame.size.height = height - bottomNavBar.frame.size.height;
contentFrame.origin.y = bottomNavBar.frame.origin.y + bottomNavBar.frame.size.height;
// Set the view's frame
contentView.frame = contentFrame;
}
#end
ViewController.m :
#implementation ViewController
-(void)viewDidAppear:(BOOL)animated {
CustomNavigationController *navigation = (CustomNavigationController*)self.navigationController;
[navigation layoutNavBar];
}
#end
Note that you have to call layoutNavBar on viewDidAppear, or the view's frame will be reset by your app. This is not a perfectly clean solution, but a pretty good fix.
I am creating an app using iOS 5, and I want to work as follows:
MainViewController w/ NavigationBar, and i set on viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
NavViewController *nav = [[NavViewController alloc] init];
[self addChildViewController:nav];
[self.view addSubview:nav.view];
}
Like a "partial view" with some nav controls (my own/custom toolbar), and at NavViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect viewRect = CGRectMake(0, 0, 320, 200);
self.view.frame = viewRect;
self.view.backgroundColor = [UIColor redColor];
}
At this point I have MainViewController with a navigation subview and still have space (in MainViewController view) that another subview can be added using the actions NavViewController.
Is there a serious flaw in this logic? or I can keep developing ?
Thnks a lot.
Now it's white dots with black background. What about if I want it to be black dots with white backgrounds?
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0)
{
return _imageArrays.count;
}// The number of items reflected in the page indicator.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0)
{
return self.intCurrentIndex;
}// The selected item reflected in the page indicator.
You can use UIAppearance to change the color of UIPageControl. Try this in your AppDelegate's didFinishLaunchingWithOptions function.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIPageControl *pageControl = [UIPageControl appearance];
pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
pageControl.backgroundColor = [UIColor blueColor];
return YES;
}
EDIT:
To apply style only to a particular view controller, use appearanceWhenContainedIn instead, as following:
UIPageControl *pageControl = [UIPageControl appearanceWhenContainedIn:[MyViewController class], nil];
Now, only UIPageControl objects contained in the MyViewController are going to adapt this style.
Thanks Mike & Shingoo!
EDIT:
If you see black background around UIPageControl at the bottom of your screen, it is due to the background color of your UIPageViewController not UIPageControl. You can change this color as following:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor]; //Set it to whatever you like
}
I don't believe that you can manipulate the UIPageViewController's page control. My solution:
I have a "root" UIViewController that is UIPageViewControllerDelegate and UIPageViewControllerDataSource.
On this root view controller, I have #property (strong, nonatomic) IBOutlet UIPageControl *pageControl. In the corresponding storyboard nib, I add a UIPageControl, position it, and check "Hides for Single Page". I can also change the colors, if I wish.
Then, I add the following in the root view controller's viewDidLoad: self.pageControl.numberOfPages = [self.features count]
My root view controller also has #property (strong, nonatomic) UIPageViewController *pageViewController. And in the implementation:
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pageViewController.delegate = self;
DataViewController *startingViewController = [self viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
self.pageViewController.dataSource = self;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
self.pageViewController.view.frame = CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height + 10.0);
[self.pageViewController didMoveToParentViewController:self];
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
(SIDE NOTE: That line that sets the frame makes the height of the UIPageViewController's view exceed the screen size so that the native page control is no longer visible. My app is portrait only, iPhone only, so I got off a bit easy here. If you need to handle rotations, you'll have to find a way to keep that native page control offscreen. I tried using auto layout, but UIPageViewController creates a set of magic views that have a bunch of autolayout mask constraints that I couldn't find a way to override.)
Anyway...then I add an extra UIPageViewController delegate method to change my new, non-native UIPageControl to the currently-selected page:
- (void)pageViewController:(UIPageViewController *)viewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if (!completed){return;}
// Find index of current page
DataViewController *currentViewController = (DataViewController *)[self.pageViewController.viewControllers lastObject];
NSUInteger indexOfCurrentPage = [self indexOfViewController:currentViewController];
self.pageControl.currentPage = indexOfCurrentPage;
}
Not as pretty as I would like, but Apple's API for this class doesn't exactly lend itself to elegance.
You can actually grab it and store it locally in your own property in one of the delegate calls.
Put this code inside your delegate to access the UIPageControl inside the UIPageViewController:
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
[self setupPageControlAppearance];
return kPageCount;
}
- (void)setupPageControlAppearance
{
UIPageControl * pageControl = [[self.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(class = %#)", [UIPageControl class]]] lastObject];
pageControl.pageIndicatorTintColor = [UIColor grayColor];
pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
}
You can recursively search it in your subviews
- (void)findAndConfigurePageControlInView:(UIView *)view
{
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIPageControl class]]) {
UIPageControl * pageControl = (UIPageControl *)subview;
//customize here
pageControl.hidesForSinglePage = YES;
break;
} else {
[self findAndConfigurePageControlInView:subview];
}
}
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
[self findAndConfigurePageControlInView:self.view];
return self.promotionsVCs.count;
}
it works for me
UIPageControl *pageControl = [UIPageControl appearanceWhenContainedIn:[MyViewController class], nil];
pageControl.pageIndicatorTintColor = [UIColor whiteColor];
pageControl.currentPageIndicatorTintColor = [UIColor redColor];
pageControl.backgroundColor = [UIColor blackColor];
This will change the appearance just for "MyViewController". If you want to have different colors in different page indicators on the same view you have to create different subviews and customize them individually.
Here's what I did in Swift 4. I tried similar answers first, in viewDidLoad, but this is what eventually worked. This same snippet was used to answer similar SO questions.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews{
if view is UIPageControl{
(view as! UIPageControl).currentPageIndicatorTintColor = .yellow
}
}
}
Once you have the UIPageControl in this block, you should be able to customize its indicator colours
If you use the "auto generated" page indicator created by UIPageViewController, I think that you can't customize it. The only way you could do that is to add an extra PageControl, either the one provided by Apple or a custom one as #Maschel proposed.
It is possible to customise it through appearance. You can do it in AppDelegate like this.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIPageControl *pageControl = [UIPageControl appearance];
pageControl.pageIndicatorTintColor = [UIColor whiteColor];
pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
pageControl.backgroundColor = [UIColor lightGrayColor];
return YES;
}
If you want to do it just for a certain view controller, replace the pageControl with this instead.
UIPageControl *pageControl = [UIPageControl appearanceWhenContainedIn:[MyViewController class], nil];
This one working perfectly for custom image
self.pageControl.pageIndicatorTintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"page_indicater"]];
self.pageControl.currentPageIndicatorTintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"page_indicater_selection"]];
You can use SMPageControl: Github. It works just like the UIPageControl but with more customisation possibilities.
You can easily access the UIPageViewController's pageControl by defining a computed property like this:
var pageControl: UIPageControl? {
for subview in view.subviews {
if let pageControl = subview as? UIPageControl {
return pageControl
}
}
return nil
}
And then customize it to suite your needs like this:
override func viewDidLoad() {
super.viewDidLoad()
pageControl?.backgroundColor = .white
pageControl?.pageIndicatorTintColor = .red
pageControl?.currentPageIndicatorTintColor = .blue
}
Obvious caveat: if Apple ever decides to change the UIPageViewController view hierarchy this will stop working.