How to add a page indicator inside the navigation bar? - ios

Like twitter did:
I have a PageViewController inside a UITabBarController which in turn is inside a UINavigationController.
Could anyone tell me how to show the page indicator inside the navigation bar?

Edit: I just figured out that navigationController.viewControllers only contains the stack.
I will post an edit in a minute
Edit 2: Well, it seems that you have to know the number of view controllers before hand.
Maybe not the best solution, but it works for me. Just tried :)
#interface ViewController () <UINavigationControllerDelegate>
#property (nonatomic, strong) UIPageControl *pageControl;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UINavigationController *navController = self.navigationController;
navController.delegate = self;
navController.navigationBar.barTintColor = [UIColor colorWithRed:.2
green:.4
blue:.9
alpha:1];
CGSize navBarSize = navController.navigationBar.bounds.size;
CGPoint origin = CGPointMake( navBarSize.width/2, navBarSize.height/2 );
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(origin.x, origin.y,
0, 0)];
//Or whatever number of viewcontrollers you have
[self.pageControl setNumberOfPages:2];
[navController.navigationBar addSubview:self.pageControl];
navController.delegate = self;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
int index = [navigationController.viewControllers indexOfObject:viewController];
self.pageControl.currentPage = index;
}
#end
Here are some screenshots.

Here's the answer for those looking for a Swift solution.
(adjust formatting to your own needs)
class NavigationControllerWithPageControl: UINavigationController, UINavigationControllerDelegate {
let numberOfPages: Int
init(rootViewController: UIViewController, numberOfPages: Int) {
self.numberOfPages = numberOfPages
super.init(rootViewController: rootViewController)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
delegate = self
navigationBar.addSubview(pageControl)
pageControl.centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor).isActive = true
pageControl.centerYAnchor.constraint(equalTo: navigationBar.centerYAnchor).isActive = true
}
lazy var pageControl: UIPageControl = {
let navBarsize = navigationBar.bounds.size
let origin = CGPoint(x: navBarsize.width/2, y: navBarsize.height/2)
let pc = UIPageControl(frame: CGRect(x: origin.x, y: origin.y, width: 100, height: 20))
pc.translatesAutoresizingMaskIntoConstraints = false
pc.numberOfPages = numberOfPages
pc.currentPageIndicatorTintColor = .black
pc.pageIndicatorTintColor = UIColor.white//.withAlphaComponent(0.3)
pc.isUserInteractionEnabled = false
return pc
}()
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let index = navigationController.viewControllers.firstIndex(of: viewController) ?? NSNotFound
DispatchQueue.main.async { [weak self] in
self?.pageControl.currentPage = index
guard let pageControlSubviews = self?.pageControl.subviews.enumerated() else {return}
for (_, dotView) in pageControlSubviews {
dotView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
dotView.layer.borderColor = UIColor.black.cgColor
dotView.layer.borderWidth = 1
}
}
}
}
Usage:
let navigationViewController = NavigationControllerWithPageControl(rootViewController: SomeViewController(), numberOfPages: 7)
present(navigationViewController, animated: true, completion: nil)

Related

Best way to position UIToolbar programmatically (with or without UIToolbarDelegate)?

I'm implementing in Playgound a segmented control underneath the navigation bar.
This seems to be a classic problem, which has been asked:
UISegmentedControl below UINavigationbar in iOS 7
Add segmented control to navigation bar and keep title with buttons
In the doc of UIBarPositioningDelegate, it says,
The UINavigationBarDelegate, UISearchBarDelegate, and
UIToolbarDelegate protocols extend this protocol to allow for the
positioning of those bars on the screen.
And In the doc of UIBarPosition:
case top
Specifies that the bar is at the top of its containing view.
In the doc of UIToolbar.delegate:
You may not set the delegate when the toolbar is managed by a
navigation controller. The default value is nil.
My current solution is as below (the commented-out code are kept for reference and convenience):
import UIKit
import PlaygroundSupport
class ViewController : UIViewController, UIToolbarDelegate
{
let toolbar : UIToolbar = {
let ret = UIToolbar()
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
// toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: 44
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
//class Toolbar : UIToolbar {
// override var barPosition: UIBarPosition {
// return .topAttached
// }
//}
let vc = ViewController()
vc.title = "Try"
vc.view.backgroundColor = .red
// Another way to add toolbar...
// let segmented = UISegmentedControl(items: ["Good", "Bad"])
// let barItem = UIBarButtonItem(customView: segmented)
// vc.toolbarItems = [barItem]
// Navigation Controller
let navVC = UINavigationController(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self)
navVC.pushViewController(vc, animated: true)
navVC.preferredContentSize = CGSize(width: 375, height: 640)
// navVC.isToolbarHidden = false
// Page setup
PlaygroundPage.current.liveView = navVC
PlaygroundPage.current.needsIndefiniteExecution = true
As you can see, this doesn't use a UIToolbarDelegate.
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
#Leo Natan's answer in the first question link above mentioned the UIToolbarDelegate, but it seems the toolbar is placed in Interface Builder.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
Try this
UIView *containerVw = [[UIView alloc] initWithFrame:CGRectMake(0, 64, 320, 60)];
containerVw.backgroundColor = UIColorFromRGB(0xffffff);
[self.view addSubview:containerVw];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 124, 320, 1)];
bottomView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomView];
UISegmentedControl *sg = [[UISegmentedControl alloc] initWithItems:#[#"Good", #"Bad"]];
sg.frame = CGRectMake(10, 10, 300, 40);
[view addSubview:sg];
for (UIView *view in self.navigationController.navigationBar.subviews) {
for (UIView *subView in view.subviews) {
[subView isKindOfClass:[UIImageView class]];
subView.hidden = YES;
}
}
By setting the toolbar's delegate and by having the delegate method return .top, you get the normal shadow at the bottom of the toolbar. If you also adjust the toolbars frame one point higher, it will cover the navbar's shadow and the final result will be what appears to be a taller navbar with a segmented control added.
class ViewController : UIViewController, UIToolbarDelegate
{
lazy var toolbar: UIToolbar = {
let ret = UIToolbar()
ret.delegate = self
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height - 1 ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: toolbar.frame.height
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .top
}
}
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
I sincerely do not know how the UIToolbarDelegate comes into play, if you change the UINavigationController.toolbar it will crashes with "You cannot set UIToolbar delegate managed by the UINavigationController manually", moreover the same will happen if you try to change the toolbar's constraint or its translatesAutoresizingMaskIntoConstraints property.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
It seems to be a reasonable question. I guess the answer for this is that you have a UIView subclass which already has the behaviour of UIToolbar, so why would we create another class-like UIToolbar, unless you just want some view below the navigation bar.
There are 2 options that I'm aware of.
1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar
The first approach might help when you have to show the toolbar in other ViewControllers that are managed by your NavigationController.
You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.
import UIKit
private var context = 0
class NavigationController: UINavigationController {
private var inToolbarFrameChange = false
var observerBag: [NSKeyValueObservation] = []
override func awakeFromNib() {
super.awakeFromNib()
self.inToolbarFrameChange = false
}
override func viewDidLoad() {
super.viewDidLoad()
observerBag.append(
toolbar.observe(\.center, options: .new) { toolbar, _ in
if !self.inToolbarFrameChange {
self.inToolbarFrameChange = true
toolbar.frame = CGRect(
x: 0,
y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
width: toolbar.frame.width,
height: toolbar.frame.height
)
self.inToolbarFrameChange = false
}
}
)
}
override func setToolbarHidden(_ hidden: Bool, animated: Bool) {
super.setToolbarHidden(hidden, animated: false)
var rectTB = self.toolbar.frame
rectTB = .zero
}
}
2) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.
import UIKit
final class ViewController: UIViewController {
private let toolbar = UIToolbar()
private let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Op 1", "Op 2"])
control.isEnabled = false
return control
}()
override func loadView() {
super.loadView()
setupToolbar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBorderLine()
}
private func setupToolbar() {
let barItem = UIBarButtonItem(customView: segmentedControl)
toolbar.setItems([barItem], animated: false)
toolbar.isTranslucent = false
toolbar.isOpaque = false
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
}
}
private extension UINavigationBar {
func showBorderLine() {
findBorderLine().isHidden = false
}
func hideBorderLine() {
findBorderLine().isHidden = true
}
private func findBorderLine() -> UIImageView! {
return self.subviews
.flatMap { $0.subviews }
.compactMap { $0 as? UIImageView }
.filter { $0.bounds.size.width == self.bounds.size.width }
.filter { $0.bounds.size.height <= 2 }
.first
}
}

move the pagecontrol dots automatically

I implement the Page controller ..automatically moving the page in swift
but my issue is the page controller dots not getting changed not indicates the page any one help me to solve this issue
here is my code
override func viewDidLoad() {
super.viewDidLoad()
UpdateCounter = 0
arrPageTitle = ["In SignUp screen user can able to input the first name, last name, emailid and password.", "After SignUp email verification link has been send to his mail then add basic profile information and sport preferences.", "In Profile setting can view profile, privacy and notifications, friends, account and championships won."];
self.pageViewController = self.storyboard?.instantiateViewController(withIdentifier: "myPageviewcontroller") as! UIPageViewController
self.pageViewController.dataSource = self
let initialContentviewcontroller = self.getViewControllerAtIndex(index: 0) as PageContentViewController
let viewcontrollers = NSArray(object: initialContentviewcontroller)
self.pageViewController.setViewControllers(viewcontrollers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRect(x: 0, y: 50, width:self.view.frame.width,height: 350)
//pagecontroller.delegate = self
pagecontroller.numberOfPages = arrPageTitle.count
pagecontroller.currentPage = 0;
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMove(toParentViewController: self)
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: (#selector(StartUpPage.advancePage)), userInfo: nil, repeats: true)
}
func getViewControllerAtIndex(index: Int) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
return self.getViewControllerAtIndex(index: index)
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int
{
return arrPageTitle.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = self.getViewControllerAtIndex(index: 0)
let index = viewController.pageIndex as Int
return index
}
func advancePage ()
{
UpdateCounter += 1
if UpdateCounter > 2 {
UpdateCounter = 0
}
var nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
if (nextviewcontroller .isEqual(nil)) {
UpdateCounter = 0
nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
}
let startingViewControllers = [nextviewcontroller]
pageViewController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
pagecontroller.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
}
Any one help me how to solve this issues when auto scroll the page ..pagecontroll dots also get moved
Thanks in advance
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = pageViewController.viewControllers?[0] as! PageContentViewController
let index = viewController.pageIndex
pagecontroller.currentPage = index
UIPageControl.appearance().pageIndicatorTintColor = UIColor.lightGray
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.red
return index
}
add this code this is delegate method for page controller
It will Work fine
Happy Code:)
try change
pagecontroller.currentPage = 0
to
pagecontroller.currentPage = UpdateCounter
You reseted the current in func AdvancedPage
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
just delete "pagecontroller.currentPage = 0" is ok.
try this (I only know objective-c codes, try to use it in swift)
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if (finished) {
YourViewControllersInPageController *childViewController = [pageViewController.viewControllers firstObject];
[self.pageControl setCurrentPage:childViewController.index];
}
}
remember set delegate
pagecontroller.delegate = self
From Another related answer
A page indicator will be visible if both methods are implemented,
transition style is 'UIPageViewControllerTransitionStyleScroll', and
navigation orientation is
'UIPageViewControllerNavigationOrientationHorizontal'. Both methods
are called in response to a 'setViewControllers:...' call, but the
presentation index is updated automatically in the case of
gesture-driven navigation.
You also need to implement below functions to set the page counts for UIPageViewController
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The number of items reflected in the page indicator.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The selected item reflected in the page indicator.
have implemented a custom page control with the help of which you can change your size , color , shape and page number very easily ... here is the class
import UIKit
import PureLayout
extension PageControl {
func setupView() {
for subview in subviews {
subview.removeFromSuperview()
}
pages.removeAll(keepingCapacity: true)
container = UIView(frame: CGRect.zero)
self.addSubview(container!)
container?.autoCenterInSuperview()
container?.autoPinEdge(toSuperviewEdge: .top)
container?.autoPinEdge(toSuperviewEdge: .bottom)
for index in 0 ..< totalPages {
let page = UIView()
container?.addSubview(page)
pages.append(page)
page.autoMatch(.width, to: .height, of: page)
page.autoPinEdge(toSuperviewEdge: .top, withInset: padding)
page.autoPinEdge(toSuperviewEdge: .bottom, withInset: padding)
if index == 0 {
page.autoPinEdge(toSuperviewEdge: .left, withInset: padding, relation: .greaterThanOrEqual)
} else if index == totalPages-1 {
page.autoPinEdge(toSuperviewEdge: .right, withInset: padding, relation: .greaterThanOrEqual)
} else {
page.autoPinEdge(.left, to: .right, of: pages[index-1], withOffset: padding)
}
}
let size = frame.height - padding * 2
let width = padding * CGFloat(totalPages+1) + size * CGFloat(totalPages)
container?.autoSetDimension(.width, toSize: width)
//layoutIfNeeded()
setNeedsDisplay()
}
}
#IBDesignable class PageControl: UIControl {
var container:UIView?
var pages = [UIView]()
#IBInspectable var currentPage:Int = 0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var allPagesColor: UIColor = Colors.greyELight {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var currentPageColor: UIColor = Colors.green {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var totalPages: Int = 3 {
didSet {
if totalPages > 0 {
setupView()
}
}
}
#IBInspectable var padding: CGFloat = 2 {
didSet {
if padding > 0 {
setupView()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
let size = frame.height - padding * 2
for (index, view) in pages.enumerated() {
view.layer.cornerRadius = size / 2
view.layer.masksToBounds = true
view.backgroundColor = (index == currentPage) ? currentPageColor: allPagesColor
}
}
}
here is the implentation
var pageControl = PageControl()
and viewDidLoad add this code
let f = CGRect(x: CGFloat(0), y: CGFloat.adjustYAxis(91), width: CGFloat.adjustXAxis(100), height: CGFloat.adjustYAxis(6))
pageControl = PageControl(frame:f)
pageControl.totalPages = 5
pageControl.tag = 716
pageControl.delegate = self
pageControl.allPagesColor = UIColor(hexString:"#afc6de")
let firstMood = self.moodsArray[0] as! MoodsModel
pageControl.currentPageColor = UIColor(hexString: firstMood.MoodBackGroundColor!)
pageControl.currentPage = 0
pageControl.padding = 10
self.pageViewController.view.addSubview(pageControl)
and change you page number in delegate methods just like that
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}

Control swipe back action in UINavigationController

I'm creating a simple flash card app as illustrated below:
I want a swipe backwards to occur like this:
To do this, onBack(index: Int) is what I need to be called when the swipe back happens (in order to update the card shown):
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var flashCardLabel: UILabel!
// Populate initial content
let content = ["Lorem", "Ipsum", "Dolor", "Sit"]
// Index of where we are in content
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
}
// Label text based on index
func setLabelToIndex() {
flashCardLabel.text = content[index]
}
// Go back
#IBAction func back(_ sender: Any) {
if index > 0 {
index = index - 1
setLabelToIndex()
}
}
// Go forward
#IBAction func next(_ sender: Any) {
if index + 1 < content.count {
index = index + 1
setLabelToIndex()
}
}
// Desired function to be called
// when swiping back in navigation stack
func onBack(index: Int) {
self.index = index
setLabelToIndex()
}
}
If I understand your question correctly, you want to be able to swipe between questions and/or have a swipe effect when you click "Next" or "Back". If that's the case, I suggest you embed your UILabel in a UIScrollView. Check this out:
class ViewController: UIViewController,UIScrollViewDelegate {
let content = ["Lorem", "Ipsum", "Dolor", "Sit"]
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 320, height: 300))
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.contentSize = CGSize(width: self.view.frame.width * content.count, height: self.scrollView.frame.size.height)
scrollView.isPagingEnabled = true
// add labels to pages
for i in 0 ..< content.count {
let label = UILabel(frame: CGRect(x: self.view.center.x * (i + 1), y: self.view.center.y, width: 100, height: 50))
label.textAlignment = .center
label.text = content[i]
scrollView.addSubview(label)
}
self.view.addSubview(scrollView)
}
// Go back
#IBAction func back(_ sender: Any) {
if index > 0 {
index = index - 1
// scroll to page
let offset = CGPoint(x: CGFloat(index) * self.view.frame.width, y: 0)
self.scrollView.setContentOffset(offset, animated: true)
}
}
// Go forward
#IBAction func next(_ sender: Any) {
if index + 1 < content.count {
index = index + 1
// scroll to page
let offset = CGPoint(x: CGFloat(index) * self.view.frame.width, y: 0)
self.scrollView.setContentOffset(offset, animated: true)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
index = round(scrollView.contentOffset.x / scrollView.frame.size.width)
}
}
Explanation:
You basically create a UIScrollView to handle the pagination effect and add a UILabel to each page with the respective text from the content array. Every time the user scrolls to a different page, index gets updated to the index of the current page. And finally, when the user clicks "Next" or "Back", you scroll over to the next or previous page
If you want it to by Push & Pop navigationcontroller, you can do it by making static index variable.
Here is code
class ViewController: UIViewController {
#IBOutlet weak var flashCardLabel: UILabel!
// Populate initial content
let content = ["Lorem", "Ipsum", "Dolor", "Sit"]
// Index of where we are in content
static var INDEX = 0;
override func viewDidLoad() {
super.viewDidLoad()
self.next(nil);
}
// Label text based on index
func setLabelToIndex() {
flashCardLabel.text = content[ViewController.INDEX]
}
// Go back
#IBAction func back(_ sender: Any?) {
if ViewController.INDEX > 0 {
ViewController.INDEX = ViewController.INDEX - 1
setLabelToIndex()
}
}
// Go forward
#IBAction func next(_ sender: Any?) {
if ViewController.INDEX + 1 < content.count {
ViewController.INDEX = ViewController.INDEX + 1
setLabelToIndex()
}
}
// Desired function to be called
// when swiping back in navigation stack
func onBack(index: Int) {
ViewController.INDEX -= 1;
//setLabelToIndex()
}
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
self.onBack(index: ViewController.INDEX);
}
}
}
i can tell you that swipe that UINavigationController suppport is the the swipe when user start swipping his finger from the left of the screen to right just to pop the view from navigation you can not push it back by swipping from right edge to left in iPhone, this is default in UINavigationController
i am writing my code as i am using you need to customize it accordinly, i didn't had time in office to edit, i will tell you more
#pragma mark for pageView
- (UIViewController *) viewControllerAtIndex:(NSUInteger)index
{
if (index > (self.imageArray.count-1))
return nil;
UIViewController *viewController = nil; ////
GalleryItems *item = self.imageArray[index];
NSString *cachedGalleryItemName = [item getCachedPhotoFileNameWithPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachedGalleryItemName])
{
ImageViewController *imageVC = [[ImageViewController alloc] initWithNibName:#"ImageViewController" bundle:nil];
imageVC.galleryItem = item;
imageVC.cachedGalleryItemName = cachedGalleryItemName;
imageVC.index = index;
viewController = imageVC;
}
else
{
if (self.downloadViewController)
{
if (self.indexOfDownloadInProgress == index)
viewController = self.downloadViewController;
else
{
FileDownloader *fileDownloader = [DataDownloadManager existingFileDownloader:cachedGalleryItemName];
if (! fileDownloader)
{
fileDownloader = [[FileDownloader alloc] init];
[fileDownloader loadURL:item.photoURL forFilePath:cachedGalleryItemName withReceipt:nil];
fileDownloader.delegate = nil;
fileDownloader.notificationName = item.contentId;
fileDownloader.queuePriority = NSOperationQueuePriorityNormal;
[[DataDownloadManager sharedInstance].operationQueue addOperation:fileDownloader];
}
}
}
else
{
DownloadViewController *downloadVC = [[DownloadViewController alloc] initWithNibName:#"DownloadViewController" bundle:nil];
downloadVC.delegate = self;
downloadVC.downloadCompleteNotificationName = item.contentId;
downloadVC.asset = item;
downloadVC.backgroundImageFileName = nil;
downloadVC.totalFileSize = nil;
downloadVC.URLString = item.photoURL;
downloadVC.cachedFileName = cachedGalleryItemName;
self.indexOfDownloadInProgress = index;
self.downloadViewController = downloadVC;
viewController = downloadVC;
}
}
return viewController;
}
Now use this function to identify the view controller
-(NSUInteger) indexOfViewController:(UIViewController *)viewController
{
NSUInteger index = nil;
if ([viewController isMemberOfClass:[ImageViewController class]])
{
ImageViewController *currentViewController = (ImageViewController *)viewController;
index = currentViewController.index;
}
else if ([viewController isMemberOfClass:[DownloadViewController class]])
index = self.indexOfDownloadInProgress;
return index;
}
- (UIViewController *)viewController:(UIViewController *)viewController ForBeforeAfter:(NSInteger) beforeAfter
{
NSUInteger index = [self indexOfViewController:viewController];
if (index == NSNotFound)
return nil;
index = index + beforeAfter;
if ([DataDownloadManager sharedInstance].internetNotAvailable)
{
while (index < self.imageArray.count - 1)
{
GalleryItems *item = self.imageArray[index];
if ([item isDownloaded])
break;
index = index + beforeAfter;
}
}
return [self viewControllerAtIndex:index];
}
now do this in page view controller delegate
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
return [self viewController:viewController ForBeforeAfter:-1];
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
return [self viewController:viewController ForBeforeAfter:+1];
}
init page view controller like this
- (void)initPageViewController:(UIViewController *)initViewController
{
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
[self.pageViewController setDataSource:self];
[self.pageViewController setViewControllers:#[initViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self.pageViewController.view setFrame:self.view.frame];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
[self.view sendSubviewToBack:self.pageViewController.view];
}
in viewDidLoad of the class(in my case it is DisplayImageViewController) you are using this page you can add this tine of code for initialization
[self initPageViewController:[self viewControllerAtIndex:self.index]];
this DisplayImageViewController class is used to display the image you just remove the UIIMAGE to something you want.
and before you push this view controller in navigation set the property like this
DisplayImageViewController *divc = initialize display view controller class; // here you just set the item in array in which you want to implement swipe
divc.imageArray = self.imageArray;
divc.galleryAsset = self.gallery;
divc.index = indexPath.item;
[self presentViewController:divc animated:YES completion:nil];

UIToolBar position to top of UINavigationController

How to move the UIToolBar to top (stick to the UINavigationBar)?
I m struggle with this thing for a long time and I've try some stuff like:
Custom UIToolBar that conforms to UIToolbarDelegate and (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar get called
and I return UIBarPositionTop but the toolbar stays at bottom.
Change the toolbar frame: self.navigationController.toolbar.frame = CGRectMake(0, NAV_BAR_Y, self.view.bounds.size.width, NAV_BAR_HEIGHT);
Custom UINaviagtionController which has this delegate function: (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar {
return UIBarPositionTop;
}
None of the struggles goes well, same look:
Any Help will be great.
(I would like to have navigation look as Apple App store navigation)
There are 2 options that I'm aware of.
1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar
You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.
import UIKit
private var context = 0
class NavigationController: UINavigationController {
private var inToolbarFrameChange = false
var observerBag: [NSKeyValueObservation] = []
override func awakeFromNib() {
super.awakeFromNib()
self.inToolbarFrameChange = false
}
override func viewDidLoad() {
super.viewDidLoad()
observerBag.append(
toolbar.observe(\.center, options: .new) { toolbar, _ in
if !self.inToolbarFrameChange {
self.inToolbarFrameChange = true
toolbar.frame = CGRect(
x: 0,
y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
width: toolbar.frame.width,
height: toolbar.frame.height
)
self.inToolbarFrameChange = false
}
}
)
}
override func setToolbarHidden(_ hidden: Bool, animated: Bool) {
super.setToolbarHidden(hidden, animated: false)
var rectTB = self.toolbar.frame
rectTB = .zero
}
}
2) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.
import UIKit
final class ViewController: UIViewController {
private let toolbar = UIToolbar()
private let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Op 1", "Op 2"])
control.isEnabled = false
return control
}()
override func loadView() {
super.loadView()
setupToolbar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBorderLine()
}
private func setupToolbar() {
let barItem = UIBarButtonItem(customView: segmentedControl)
toolbar.setItems([barItem], animated: false)
toolbar.isTranslucent = false
toolbar.isOpaque = false
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
}
}
private extension UINavigationBar {
func showBorderLine() {
findBorderLine().isHidden = false
}
func hideBorderLine() {
findBorderLine().isHidden = true
}
private func findBorderLine() -> UIImageView! {
return self.subviews
.flatMap { $0.subviews }
.compactMap { $0 as? UIImageView }
.filter { $0.bounds.size.width == self.bounds.size.width }
.filter { $0.bounds.size.height <= 2 }
.first
}
}
Try this solution
#interface ViewController () <UIToolbarDelegate>
{
UIToolbar * lpToolbar;
}
#end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
lpToolbar = [[UIToolbar alloc] initWithFrame :CGRectZero];
lpToolbar.delegate = self;
self.navigationItem.title = #"Title";
}
-(void) viewWillAppear :(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController.view addSubview :lpToolbar];
CGRect rFrame = self.navigationController.navigationBar.frame;
lpToolbar.frame = CGRectMake( 0.0, rFrame.origin.y + rFrame.size.height, rFrame.size.width, 50.0 );
}
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[lpToolbar removeFromSuperview];
}
-(UIBarPosition) positionForBar:(id <UIBarPositioning>)bar
{
return UIBarPositionTop;
}

How to resize UINavigationController's view controllers frame after it is resized

I have a UINavigationController that is added as a child view controller to a container UIViewController class. I need to dynamically display a banner view above the navigation controller's view. I resize the navigation controller's view but I'm unable to resize the navigation controller's child view controller's views.
// this code updates the navigation controller's view, but it does not update the height of its child view controller's view.
self.updateViewYOffset(self.rootNavigationController.view, yOffset: yOffset)
func updateViewYOffset(view: UIView, yOffset: CGFloat) {
var frame = view.frame
frame.origin.y = yOffset
frame.size.height = self.view.frame.size.height - yOffset
view.frame = frame
}
// if iPhone 5s - the navigation controller's view height is now 538 (568-30) but its child view controller's views height is still 568.
I just implemented something like this. You may have already figured out a solution, but for future visitors, I'll explain what I did.
I created a custom subclass of UIViewController, we'll call it AppContainerController, that actually sets up the top banner view (as a UIButton in my case) as a subview. Then I have a method -setMainViewController(controller:UIViewController) that adds that controller's view to my AppContainerController and sets the frame to fill the entire view.
In my AppDelegate, I set this AppContainerController as the window's rootViewController. Then I add a UINavigationController using the AppContainerController#setMainViewController(...). When I want to show the banner view, I can resize the frame of the mainViewController's view, and allow the banner (UIButton) frame to be larger. All of this can be animated. Here's the code for the AppContainerController.
class KLAppContainerController: UIViewController {
var activeTourBanner:UIButton!
var mainViewController:UIViewController?
let bannerHeight:CGFloat = 44.0
let s = String(format: "s%#a%#u%#B%#r%#i%#d%#w", "t", "t", "s", "a", "W", "n", "o")
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .CurrentContext
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
func setMainViewController(controller:UIViewController) {
var topViewFrame = self.view.bounds
if let mvc = self.mainViewController {
topViewFrame = mvc.view.frame
mvc.view.frame = self.view.bounds
mvc.view.removeFromSuperview()
mvc.willMoveToParentViewController(nil)
mvc.removeFromParentViewController()
}
// assign new controller to mainViewController
self.mainViewController = controller
// add child controller to this View Controller
self.addChildViewController(self.mainViewController!)
self.mainViewController!.didMoveToParentViewController(self)
self.mainViewController!.view.autoresizingMask = autoResizeToFillScreen()
self.mainViewController!.view.frame = topViewFrame
self.view.addSubview(self.mainViewController!.view)
}
func autoResizeToFillScreen() -> UIViewAutoresizing {
return (.FlexibleWidth |
.FlexibleHeight |
.FlexibleTopMargin |
.FlexibleBottomMargin |
.FlexibleLeftMargin |
.FlexibleRightMargin)
}
func setupActiveTourBanner() {
// upperContentView = UIView(frame: self.view.bounds)
//self.view.addSubview(upperContentView)
self.activeTourBanner = UIButton.buttonWithType(.Custom) as UIButton
self.activeTourBanner.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), bannerHeight)
self.activeTourBanner.setTitle("Tap here to continue the tour", forState: .Normal)
NUIRenderer.renderButton(self.activeTourBanner, withClass:"ActiveTourButton")
self.activeTourBanner.addTarget(self, action: "activeTourButtonTapped", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(self.activeTourBanner)
self.activeTourBanner.hidden = true
self.hideActiveTourBanner()
}
func showActiveTourBanner(tour:KLTour) {
let bannerRect = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), bannerHeight)
let controllerRect = CGRectMake(0, bannerHeight, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds) - bannerHeight)
var sw = UIApplication.sharedApplication().valueForKey(s) as UIWindow
self.activeTourBanner.hidden = false
UIView.animateWithDuration(0.2, animations: { () -> Void in
sw.frame = CGRectMake(0, self.bannerHeight, CGRectGetWidth(sw.frame), CGRectGetHeight(sw.frame))
self.activeTourBanner.frame = bannerRect
self.mainViewController!.view.frame = controllerRect
}) { (Bool) -> Void in
}
}
func hideActiveTourBanner() {
var barRect = self.activeTourBanner.frame
barRect.origin.y = -barRect.size.height
let controllerRect = self.view.bounds
var sw = UIApplication.sharedApplication().valueForKey(s) as UIWindow
UIView.animateWithDuration(0.2, animations: { () -> Void in
self.mainViewController!.view.frame = controllerRect
self.activeTourBanner.frame = barRect
sw.frame = CGRectMake(0, 0, CGRectGetWidth(sw.frame), CGRectGetHeight(sw.frame))
}, completion: { (Bool) -> Void in
self.activeTourBanner.hidden = true
})
}
// MARK: - Handlers
func activeTourButtonTapped() {
self.hideActiveTourBanner()
}
}

Resources