i want to create a navigation bar in which I have a pageControl as well as the Title in the navigation bar just like the twitter iOS app contains, I got them both by setting the
self.navigationItem.Title = #"Home";
and achieved the PageControl by creating an instance of UIPageControl and setting its centre to the centre of the navigation bar and achieving its navigation detection by using the UINavigationController's Delegate. But the problem is that the pageControl and the title are overlapping each other.. how can I set it like this that the Title should appear on the top of page indicator. Here is the code of the positioning the pageControl.
self.navigationController.delegate = self;
CGSize navBarSize = self.navigationController.navigationBar.bounds.size;
CGPoint origin = CGPointMake( navBarSize.width/2, navBarSize.height/2 );
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(origin.x, origin.y,
0, 0)];
[self.pageControl setNumberOfPages:3];
[self.navigationController.navigationBar addSubview:self.pageControl];
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
int index = [self.navigationController.viewControllers indexOfObject:viewController];
self.pageControl.currentPage = index; }
I figured it out, both (the title and the pageControl) were overlapping each other so i just changed the Y-Axis of the pageControl and
self.navigationController.delegate = self;
CGSize navBarSize = self.navigationController.navigationBar.bounds.size;
CGPoint origin = CGPointMake( navBarSize.width/2, navBarSize.height/2 );
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(origin.x, origin.y+45,
0, 0)]; //Here added 45 to Y and it did the trick
[self.pageControl setNumberOfPages:3];
[self.navigationController.navigationBar addSubview:self.pageControl];
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
int index = [self.navigationController.viewControllers indexOfObject:viewController];
self.pageControl.currentPage = index; }
Related
This is my question
CONTEXT
I have a ViewController which I have an effect where the Navigation Bar gets transparent when the user go down in the scroll, and the Navigation Bar gets normal when the user go up in scroll view. This effect I did with the UIScrollViewDelegate's methods. This is the code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offset = scrollView.contentOffset.y;
if (scrollView.contentOffset.y < 0){
scrollView.bounces = false;
}else{
scrollView.bounces = true;
}
CGFloat currentAlpha = (offset / 310);
if (currentAlpha < 0) {
self.navigationController.navigationBar.translucent = YES;
self.navigationController.navigationBar.alpha = 1;
self.navigationController.titleNavBar.alpha = 0; //This property I made in an UINavigationController extension
} else {
self.navigationController.navigationBar.translucent = YES;
self.navigationController.navigationBar.alpha = 1;
self.navigationController.titleNavBar.alpha = currentAlpha; //This property I made in an UINavigationController extension
[self.navigationController.navigationBar setBackgroundColor:[UIColor colorWithRed:0.0f/0.0f green:136.0f/255.0 blue:206.00f/255.0f alpha:currentAlpha]];
}
}
With the previous code I got the effect, but I have a problem: I can't add it to the status bar because self.navigationController.navigationBar.translucent is set on YES. So, in the iPhones, the status bar shows transparent and in the iPhone X shows bigger this transparence than another iPhones (see the image).
Anyone know how can I do this transparent effect with Navigation Bar and the Statsus Bar?
You need to change statusBar UIView color with NavigationBar color.
Create AppDelegate SharedInastance :
+ (AppDelegate *)sharedAppDelegate {
return (AppDelegate *)[UIApplication sharedApplication].delegate;
}
Add below code in AppDelegate to get status bar view:
- (UIView *)statusBarView {
return [[[UIApplication sharedApplication] valueForKey:#"statusBarWindow"] valueForKey:#"statusBar"];
}
Add below code when you want to change status bar color:
[[AppDelegate sharedAppDelegate] statusBarView].backgroundColor = [UIColor redColor];
In order to fix the overlapping of status bar and navigation bar in iOS 7+, i'm using this code inside didFinishLaunchingWithOptions in AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//some codes
//.
//.
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{
UIView *FakeNavBar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 20)];
FakeNavBar.backgroundColor = UIColorFromRGB(0x55BCAFF);
float navBarHeight = 20.0;
for (UIView *subView in self.window.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + navBarHeight, subView.frame.size.width, subView.frame.size.height - navBarHeight);
} else {
subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + navBarHeight, subView.frame.size.width, subView.frame.size.height);
}
}
[self.window addSubview:FakeNavBar];
}
}
It pushes all my controllers and views 20 pixels down and and the overlapping problem gets fixed but when i reach my tab view controller scene, then the tab bar on the bottom goes out of view by 20 pixels.
So how can i keep the tab bar in its place while shifting everything else down?
It would also work if i could just shift up only the tab bar by 20 pixels.
I was able to shift only the tab bar 20 pixels up but this may put some views behind the tab bar which is unwanted.
here is the code written inside viewDidAppear of my UITabBarController class :
-(void)viewDidAppear:(BOOL)animated{
CGRect newFrame = self.tabBar.frame;
newFrame.origin.y -= 20;
self.tabBar.frame = newFrame;
}
Please use the code below -
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{
UIView *FakeNavBar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 20)];
FakeNavBar.backgroundColor = UIColorFromRGB(0x55BCAFF);
float navBarHeight = 20.0;
for (UIView *subView in self.window.subviews) {
for (int index=0; index<[tabBarController.viewControllers count]; index++)
{
if ([subView isKindOfClass:[[(UIViewController*)[tabBarController.viewControllers objectAtIndex:index] view] class]])
{
continue;
}
}
if ([subView isKindOfClass:[UIScrollView class]]) {
subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + navBarHeight, subView.frame.size.width, subView.frame.size.height - navBarHeight);
} else {
subView.frame = CGRectMake(subView.frame.origin.x, subView.frame.origin.y + navBarHeight, subView.frame.size.width, subView.frame.size.height);
}
}
[self.window addSubview:FakeNavBar];
}
I assume that you're not using a navigation controller and you've manually added the navigation bar in your view. Is that right?
You should be able to achieve what you're after by adding a view with a size of the status bar at the top of the views of your view controllers in the storyboard.
Are you using auto-layout constraints? You could use them to make these views stick to the top of your views, have a fixed height of 20 pixels and a width equal to the width of the view controller's view.
I have implemented a niceUIPageViewControl with aPageControl. When swiping the indicator changes and shows the correct current page.
However i noticed that all that it takes for the current page indicator to switch is to start swiping. Meaning if i start swiping but then let go of the finger the current page indicator has switched like the page has been switched however it has not. This is the code that i am using to make the switch:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
NSUInteger index = [self.navigationController.viewControllers indexOfObject:viewController];
self.pageControl.currentPage = index;
}
Another thing i noticed was that when i swipe right and left changing views FAST the page indicator is just stuck and does not move.
So it only works when you are not changing views fast. If you need any additional code let me know. Thank you.
Edit
This is the code i use to implement my UIPageViewController.
- (void)viewDidLoad
{
[super viewDidLoad];
// Create the data model
self.navigationItem.title = #"Tabell";
self.identifiers = [[NSMutableArray alloc] init];
[self.identifiers addObject:#"rankTable"];
[self.identifiers addObject:#"page2"];
// Create page view controller
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;
self.navigationController.delegate = self;
CGSize navBarSize = self.navigationController.navigationBar.bounds.size;
CGPoint origin = CGPointMake( navBarSize.width/2, navBarSize.height/2 );
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(origin.x, origin.y+16,
0, 0)]; //Here added 45 to Y and it did the trick
self.pageControl.pageIndicatorTintColor = navbarColor;
self.pageControl.currentPageIndicatorTintColor = [UIColor lightGrayColor];
[self.pageControl setNumberOfPages:2];
[self.navigationController.navigationBar addSubview:self.pageControl];
UITableViewController *startingViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
CGFloat tabBarHeight = self.tabBarController.tabBar.frame.size.height;
// Change the size of page view controller
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - tabBarHeight);
[self addChildViewController:_pageViewController];
[self.view addSubview:_pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
Why don't you implement the UIPageViewControllerDelegate's
- pageViewController: didFinishAnimating: previousViewControllers: transitionCompleted: method? It has a transitionCompleted boolean which tells you if you should update your page control. Your current implementation seems to be buggy because as soon as you star swiping a page, it loads a view controller for the next page and calls the ...didShow... method. Hope this hepls.
I'd like to have an underline that indicates which item was selected. It slides to any other items whenever the item was tapped. Therefore, I added a subview to the custom UITabBarController and set the animation. Then I use hidesBottomBarWhenPushed to hide the tab bar when pushed. However, the underline seems not combined with the custom UITabBarController.
How to handle the subview so it is always on top even when using the back gesture? This Flipboard app capture is what I want to do.
Edit:
CustomTabBarController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// create underline view
CGRect tabBarFrame = self.tabBar.frame;
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarFrame) / MIN(5, self.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * self.selectedIndex;
CGRect underlineFrame = CGRectMake(originX, CGRectGetMaxY(tabBarFrame) - 3.0f, itemWidth, 3.0f);
self.underlineView = [[UIView alloc] initWithFrame:underlineFrame];
self.underlineView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.underlineView];
}
#pragma mark - UITabBarDelegate
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSUInteger itemIndex = [tabBar.items indexOfObject:item];
CGRect underlineFrame = self.underlineView.frame;
CGFloat originX = (CGFloat)CGRectGetWidth(self.underlineView.frame) * itemIndex;
// underline shifting animation
[UIView animateWithDuration:0.25
animations:^{
self.underlineView.frame = CGRectMake(originX, underlineFrame.origin.y, CGRectGetWidth(underlineFrame), CGRectGetHeight(underlineFrame));
}];
}
CustomTableViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *detailViewController = segue.destinationViewController;
detailViewController.hidesBottomBarWhenPushed = YES;
}
hidesBottomBarWhenPushed hides the tab bar but its subview (the underline view).
If I hide it by myself and show it in viewWillAppear, the underline view does not look like on top of the tab bar.
I finally found a workaround. To override the method hidesBottomBarWhenPushed, then you can add an alternative view for tab bar's subviews.
sourceViewController.m
- (BOOL)hidesBottomBarWhenPushed
{
[super hidesBottomBarWhenPushed];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
CGRect tabBarBounds = tabBarController.tabBar.bounds;
CGFloat underlineHeight = CGRectGetHeight(tabBarController.underlineView.frame);
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarBounds) / MIN(5, tabBarController.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * tabBarController.selectedIndex;
UIView *alternativeView = [[UIView alloc] initWithFrame:CGRectMake(originX,
CGRectGetMaxY(tabBarBounds) - underlineHeight,
itemWidth,
underlineHeight)];
alternativeView.tag = tabBarController.underlineViewTag;
alternativeView.backgroundColor = tabBarController.underlineView.backgroundColor;
[tabBarController.tabBar addSubview:alternativeView];
}
return NO;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
tabBarController.underlineView.hidden = NO;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
[alternativeView removeFromSuperview];
}
}
Don't forget the case that interactivePopGesture failure to popover view controller, the alternative view still be added to tab bar. So remove it at destination view controller if needed.
destinationViewController.m
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
if (alternativeView) [alternativeView removeFromSuperview];
}
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.