Control swipe back action in UINavigationController - ios

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];

Related

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)
}

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;
}

page based application overload memory ARC don´t release pages

may you can help me with this memory issue. So I builded a app based on the project preset "Page-Based Application" everything works well, but over time the single views load every Viewcontroller into the physical memory and don´t release them. If the memory is full the app will crash.
here ist my code:
The RootViewController (CatalougeViewController):
import UIKit
class CatalougeViewController: UIViewController, UIPageViewControllerDelegate, UIGestureRecognizerDelegate {
var pageViewController: UIPageViewController?
var zoomTransform: CGAffineTransform?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = UIPageViewController(transitionStyle: .PageCurl, navigationOrientation: .Horizontal, options: nil)
self.pageViewController!.delegate = self
let startingViewController: DataViewController = self.modelController.viewControllerAtIndex(0, storyboard: self.storyboard!)!
let viewControllers = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: {done in })
self.pageViewController!.dataSource = self.modelController
self.addChildViewController(self.pageViewController!)
self.view.addSubview(self.pageViewController!.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect = self.view.bounds
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
pageViewRect = CGRectInset(pageViewRect, 0.0, 0.0)
}
self.pageViewController!.view.frame = pageViewRect
self.pageViewController!.didMoveToParentViewController(self)
/*
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController!.gestureRecognizers
*/
self.view.gestureRecognizers = self.pageViewController?.gestureRecognizers
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: "pinchDetected:")
self.view.addGestureRecognizer(pinchRecognizer)
let panRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
panRecognizer.minimumNumberOfTouches = 2
panRecognizer.maximumNumberOfTouches = 2
self.view.addGestureRecognizer(panRecognizer)
}
func pinchDetected(pinchRecognizer: UIPinchGestureRecognizer) {
if (UIGestureRecognizerState.Began == pinchRecognizer.state) || (UIGestureRecognizerState.Changed == pinchRecognizer.state) {
// Use the x or y scale, they should be the same for typical zooming (non-skewing)
let curScale = pinchRecognizer.view!.layer.valueForKeyPath("transform.scale.x")!.floatValue
let currentScale = CGFloat(curScale!)
// Variables to adjust the max/min values of zoom
let minScale: CGFloat = 1.0;
let maxScale: CGFloat = 2.0;
let zoomSpeed: CGFloat = 0.5;
var deltaScale: CGFloat = pinchRecognizer.scale
// You need to translate the zoom to 0 (origin) so that you
// can multiply a speed factor and then translate back to "zoomSpace" around 1
deltaScale = ((deltaScale - 1) * zoomSpeed) + 1
// Limit to min/max size (i.e maxScale = 2, current scale = 2, 2/2 = 1.0)
// A deltaScale is ~0.99 for decreasing or ~1.01 for increasing
// A deltaScale of 1.0 will maintain the zoom size
deltaScale = min(deltaScale, maxScale / currentScale)
deltaScale = max(deltaScale, minScale / currentScale)
zoomTransform = CGAffineTransformScale(pinchRecognizer.view!.transform, deltaScale, deltaScale);
pinchRecognizer.view!.transform = zoomTransform!;
// Reset to 1 for scale delta's
// Note: not 0, or we won't see a size: 0 * width = 0
pinchRecognizer.scale = 1;
}
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: self.view)
if recognizer.state == UIGestureRecognizerState.Ended {
// 1
let velocity = recognizer.velocityInView(self.view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
let slideMultiplier = magnitude / 200
// print("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
// 2
let slideFactor = 0.1 * slideMultiplier //Increase for more of a slide
// 3
var finalPoint = CGPoint(x:recognizer.view!.center.x + (velocity.x * slideFactor),
y:recognizer.view!.center.y + (velocity.y * slideFactor))
// 4
finalPoint.x = min(max(finalPoint.x, 0), self.view.bounds.size.width)
finalPoint.y = min(max(finalPoint.y, 0), self.view.bounds.size.height)
// 5
UIView.animateWithDuration(Double(slideFactor),
delay: 0,
// 6
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var modelController: CatalougeViewControllerModel {
// Return the model controller object, creating it if necessary.
// In more complex implementations, the model controller may be passed to the view controller.
if _modelController == nil {
_modelController = CatalougeViewControllerModel()
}
return _modelController!
}
var _modelController: CatalougeViewControllerModel? = nil
// MARK: - UIPageViewController delegate methods
func pageViewController(pageViewController: UIPageViewController, spineLocationForInterfaceOrientation orientation: UIInterfaceOrientation) -> UIPageViewControllerSpineLocation {
if (orientation == .Portrait) || (orientation == .PortraitUpsideDown) || (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
// In portrait orientation or on iPhone: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to true, so set it to false here.
let currentViewController = self.pageViewController!.viewControllers![0]
let viewControllers = [currentViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: {done in })
self.pageViewController!.doubleSided = false
return .Min
}
// In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
let currentViewController = self.pageViewController!.viewControllers![0] as! DataViewController
var viewControllers: [UIViewController]
let indexOfCurrentViewController = self.modelController.indexOfViewController(currentViewController)
if (indexOfCurrentViewController == 0) || (indexOfCurrentViewController % 2 == 0) {
let nextViewController = self.modelController.pageViewController(self.pageViewController!, viewControllerAfterViewController: currentViewController)
viewControllers = [currentViewController, nextViewController!]
} else {
let previousViewController = self.modelController.pageViewController(self.pageViewController!, viewControllerBeforeViewController: currentViewController)
viewControllers = [previousViewController!, currentViewController]
}
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: {done in })
return .Mid
}
}
Thats the ViewControllerModel (CatalougeViewControllerModel):
class CatalougeViewControllerModel: NSObject, UIPageViewControllerDataSource {
override init() {
super.init()
}
func viewControllerAtIndex(index: Int, storyboard: UIStoryboard) -> DataViewController? {
// Return the data view controller for the given index.
if (PDF3.imagePath.count == 0) || (index >= PDF3.imagePath.count) {
return nil
}
// Create a new view controller and pass suitable data.
let dataViewController = storyboard.instantiateViewControllerWithIdentifier("DataViewController") as! DataViewController
dataViewController.dataObject = PDF3.imagePath[index]
return dataViewController
}
func indexOfViewController(viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return PDF3.imagePath.indexOf(viewController.dataObject!) ?? NSNotFound
}
// MARK: - Page View Controller Data Source Seiten werden hochgezählt
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index--
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if index == NSNotFound {
return nil
}
index++
if index == PDF3.imagePath.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
}
Thank you for your time to help me...
There exists a method on iOS that gets called whenever a device's memory gets constrained. By default, only the UIApplicationDelegate's gets called, but you can use NSNotificationCenter to add any class and/or object so they also get called.
When the method is called, it's a signal for your app to free some memory. You can then purge data that you don't need anymore.
I fixed my problem. So sorry guys the solution was in the DataViewController and I doesn´t posted my DataViewController file so anyway.
Here is my solution:
old DataViewController:
import UIKit
class DataViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
var dataObject: String?
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.image = UIImage(named: dataObject!)
// my Images was cached because I used "named:"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Thats the new DataViewController:
import UIKit
class DataViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
var dataObject: String?
override func viewDidLoad() {
super.viewDidLoad()
autoreleasepool { () -> () in
self.myImageView.image = UIImage(contentsOfFile: dataObject!) // with "contentsOfFile:" the images are uncached...
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

how to make uipageviewcontroller go in circles

I'm having a UIPageViewController that shows different pages. The current behaviour is that it stops scrolling when I reached the last page. What I now want to achieve is that when on the last page and scrolling right, it goes to the first page. When at the first page, go to the last page when scrolling left. So basically let the PageViewController show the pages in circles. My first approach works quite well while having more than one page, or starting with one single page:
- (UIViewController*) pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(TMCollectionViewController *)viewController
{
if((viewController.pageIndex) >= 1)
{
return _viewControllers[viewController.pageIndex - 1];
}
else
{
if ([_viewControllers count] == 1) {
return nil;
}
return [_viewControllers objectAtIndex:[_viewControllers count]-1];
}
}
But when I remove the pages so that only one is left, it still remembers either the before or after page, and shows it accordingly. Even though there is only one page left in the array. Any help is highly appreciated.
You can implement it as
When you are on 1st index and swipe left make its index = total count of pages
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = ((PageContentViewController*) viewController).pageIndex;
if ((index == 0) || (index == NSNotFound)) {
index = self.pageTitles.count;
}
index--;
return [_viewControllers objectAtIndex:index];
}
When you are on last index set it to zero index
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = ((PageContentViewController*) viewController).pageIndex;
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageTitles count]) {
index = 0;
}
return [_viewControllers objectAtIndex:index];
}
Thanks, #Roshni
Swift 2 - Roshni's solution
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index: Int = (viewController as! PageContentViewController).index
if index == 0 || index == NSNotFound {
index = self.pageTitles.count
}
index--
return viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index: Int = (viewController as! PageContentViewController).index
if index == NSNotFound {
return nil
}
index++
if index == self.pageTitles.count {
index = 0
}
return viewControllerAtIndex(index)
}
here is sample, I did for circular pagination
I was using Using UIPageViewController to create a content slider (Objective-C/Swift) article but thanks to #Tom Calmon and #Roshani now I am able to achieve my desired result on Swift 2.2
class PageHomeViewController: UIViewController , UIPageViewControllerDataSource{
// MARK: - Variables
private var pageViewController: UIPageViewController?
// Initialize it right away here
private let contentImages = ["nature_pic_1.png",
"nature_pic_2.png",
"nature_pic_3.png",
"nature_pic_4.png"];
//MARK: - View controller life cycle methods
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
}
//MARK: - Create and start page view controller
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
pageController.dataSource = self
if contentImages.count > 0 {
let firstController = getItemController(0)!
let startingViewControllers: NSArray = [firstController]
pageController.setViewControllers(startingViewControllers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController!.didMoveToParentViewController(self)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
appearance.backgroundColor = UIColor.darkGrayColor()
}
// MARK: - UIPageViewControllerDataSource
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index: Int = (viewController as! PageContentViewController).itemIndex
if index == 0 || index == NSNotFound {
index = contentImages.count
}
index -= 1
return getItemController(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?{
var index: Int = (viewController as! PageContentViewController).itemIndex
if index == NSNotFound {return nil}
index += 1
if index == contentImages.count {index = 0}
return getItemController(index)
}
private func getItemController(itemIndex: Int) -> PageContentViewController? {
if itemIndex < contentImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = contentImages[itemIndex]
return pageItemController
}
return nil
}
// MARK: - Page Indicator
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return contentImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
Hope this will help out there someone , someday!!

How to add a page indicator inside the navigation bar?

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)

Resources