Cannot show UICollectionView with Segmented Control - ios

In my main view Controller, I have a segmented control and a container view below. I want to change the container view whenever I switch the segmented control. When I switch over the tab, I can see the very view being loaded (print in viewDidload), however, I find it not working with the datasource of collectionViewDataSource. Any ideas?
// Main View
let videoViewController = VideoViewController()
let photoViewController = PhotoViewController()
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(oldValue)
updateActiveViewController()
}
}
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
activeViewController = videoViewController
}
#IBAction func segmentDidChanged(sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
print("\(sender.titleForSegmentAtIndex(0)!) Selected")
activeViewController = videoViewController
} else if sender.selectedSegmentIndex == 1 {
print("\(sender.titleForSegmentAtIndex(1)!) Selected")
activeViewController = photoViewController
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMoveToParentViewController(nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = containerView.bounds
containerView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMoveToParentViewController(self)
}
}
// PhotoView
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("HI")
}
extension PhotoViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as!PhotoCollectionViewCell
cell.backgroundColor = UIColor.blackColor()
// Configure the cell
cell.imgView.image = UIImage(named: "test")
return cell
}
}
When I make the PhotoViewController as initial controller, it works.

I have used the UIContainerView and UISegmentControl, if you use ContainerView show single UIViewController are already posted and the link are given below,
When I add a UIContainerView to a view controller it's type is UIView. How do I get to the viewcontroller for the embedded view?
or If you use ContainerView show two UIViewController when alernatively click UISegmentControl, to show single viewcontroller code is given below,
If you create the storyboard, then go MainViewController Class and create the outlet are
#property (weak, nonatomic) IBOutlet UIView *containerView;
#property (weak, nonatomic) IBOutlet UISegmentedControl *segmentControl;
#property (weak, nonatomic) UIViewController *currentViewController;
ViewDidLoad method:
UIFont *font = [UIFont boldSystemFontOfSize:16.0f];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:font
forKey:UITextAttributeFont];
[segmentControl setTitleTextAttributes:attributes
forState:UIControlStateNormal];
_currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
_currentViewController.view.layer.cornerRadius = 8.0f;
_currentViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addChildViewController:_currentViewController];
[self addSubview:_currentViewController.view toView:_containerView];
And UISegmentController method is given below,
-(IBAction)indexChanged:(UISegmentedControl *)sender
{
if (sender.selectedSegmentIndex == 0) {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
else if (sender.selectedSegmentIndex == 1)
{
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
}
And the two view related Methods are,
- (void)addSubview:(UIView *)subView toView:(UIView*)parentView {
[parentView addSubview:subView];
NSDictionary * views = #{#"subView" : subView,};
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
}
- (void)cycleFromViewController:(UIViewController*) oldViewController
toViewController:(UIViewController*) newViewController {
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
[self addSubview:newViewController.view toView:self.containerView];
[newViewController.view layoutIfNeeded];
// set starting state of the transition
newViewController.view.alpha = 0;
[UIView animateWithDuration:0.5
animations:^{
newViewController.view.alpha = 1;
oldViewController.view.alpha = 0;
}
completion:^(BOOL finished) {
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
}
if you use these code alternativelly open the ViewController from UIContainerView when click SegmentControl, if work CollectionView from FirstViewController and its working for me!
Hope its helpful..

Related

View not removed from its superview in iOS

I have a UIView and a collectionView. If there is an internet connection I want to hide the collectionView and show the UIView, if not otherwise.
class MyClass{
#IBOutlet weak var collectionView: UICollectionView!
var myView : CustomView?
....
func internetStatusChanegd(){
if(isOnline){
collectionView.isHidden = true
if let viewNib = UIView.loadFromNibNamed("CustomView", bundle: Bundle.main) as? CustomView {
myView = viewNib
myView!.frame = self.view.bounds
self.view.addSubview(myView!)
}
}else{
if let customView = myView{
customView.removeFromSuperview()
}
collectionView.isHidden = false
}
}
}
removeFromSuperview() Is called but the view is not removed from the view. Do you have an idea about the problem?
While adding a sub view in my view give a tag to that view.
Iterate the for loop for subviews in view.
While removing just check if it's the view with same tag then call-
self.removeFromSuperview()
Please remove already available view before you are adding new view.
func internetStatusChanegd() {
if(isOnline) {
collectionView.isHidden = true
for subView in (self.view.subviews)! {
if (subView.tag == 100) {
subView.removeFromSuperview() //this will remove already available object form self.view
}
}
if let viewNib = UIView.loadFromNibNamed("CustomView", bundle: Bundle.main) as? CustomView {
myView = viewNib
myView!.frame = self.view.bounds
myView.tag = 100 //add tag when you create object
self.view.addSubview(myView!)
}
}else{
if let customView = myView{
customView.removeFromSuperview()
}
collectionView.isHidden = false}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if isOnline{
collectionView.backgroundView = myView //your custom view whatever you want to show here like : button..
return 0
}
collectionView.backgroundView = nil
return array.count
}
func internetStatusChanegd(){ collectionView.reloadData() } //it'll handle automatically that view .
Try this
Remove like this
for subview in self.view.subviews{
if subview is CustomView
{
subview.removeFromSuperview()
}
}

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

Wrapping cells in UICollectionview iOS 8

I have the same problem with this question. I already tried the solution but it is not called. Where should I implement or call the method or subclass of UICollectionViewFlowLayout.
Where should I use this?
Thanks in advance.
U can do like below, this method is called automatically,
swift version
first create a new class which is the subclass of UICollectionViewFlowLayout for example
import UIKit
class CustomLayout: UICollectionViewFlowLayout
{
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var newAttributes:[AnyObject] = []
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
super.layoutAttributesForElementsInRect(rect)
var attributes:[AnyObject] = super.layoutAttributesForElementsInRect(rect)!
//arrayWithCapacity(attributes.count)
//configure your attributes for each item hear and store it in separate array and return that array in below example i am sending the same attributes.
return attributes
}
}
in ViewController class
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var aCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var customLayout:CustomLayout = CustomLayout() //initilise the custom layout for collection view
customLayout.minimumLineSpacing = 0.33 //set the offset between items
customLayout.minimumInteritemSpacing = 0.0
customLayout.itemSize = CGSizeMake(50.0, 50.0)
aCollectionView.collectionViewLayout = customLayout //set it to collection view
var cellNib:UINib = UINib(nibName: "CollectionViewCell", bundle: nil)
aCollectionView.registerNib(cellNib, forCellWithReuseIdentifier: "CELL")
}
objective-c version
in xcode crate a new file by subclassing the UICollectionViewFlowLayout lets say it's name as MyCustomCollectionViewFlowLayout and in MyCustomCollectionViewFlowLayout .m file place the code
#import "MyCustomCollectionViewFlowLayout.h"
#implementation MyCustomCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
[super layoutAttributesForElementsInRect:rect];
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
for (UICollectionViewLayoutAttributes *attribute in attributes)
{
if ((attribute.frame.origin.x + attribute.frame.size.width <= ceil(self.collectionViewContentSize.width)) &&
(attribute.frame.origin.y + attribute.frame.size.height <= ceil(self.collectionViewContentSize.height)))
{
[newAttributes addObject:attribute];
}
}
return newAttributes;
}
- (void)dealloc
{
[super dealloc];
}
#end
and the class where u are using the collection view just import MyCustomCollectionViewFlowLayout.h this and set it to collection view
- (void)viewDidLoad
{
[super viewDidLoad];
//....other codes
MyCustomCollectionViewFlowLayout *flowLayout = [[MyCustomCollectionViewFlowLayout alloc]init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
//set according to your settings
flowLayout.minimumInteritemSpacing = 0.0f;
flowLayout.minimumLineSpacing = 0.33f; //set the offset between items
_collectionView.pagingEnabled = YES;
_collectionView.bounces = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.showsVerticalScrollIndicator = NO;
[_collectionView setCollectionViewLayout:flowLayout]; //set your custom flow layout hear
[_collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:cellIdentifier]; //set the custom cell
}

Navigation pop view when swipe right like Instagram iPhone app.How i achieve this?

I want to pop a view when swipe right on screen or it's work like back button of navigation bar.
I am using:
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
This single line of code for pop navigation view and it's a work for me but when i swipe form middle of screen this will not work like Instagram iPhone app.
Here i give a one screen of Instagram app in that you can see the Example of swipe right pop navigation view:
Apple's automatic implementation of the "swipe right to pop VC" only works for the left ~20 points of the screen. This way, they make sure they don't mess with your app's functionalities. Imagine you have a UIScrollView on screen, and you can't swipe right because it keeps poping VCs out. This wouldn't be nice.
Apple says here :
interactivePopGestureRecognizer
The gesture recognizer responsible for popping the top view controller off the navigation stack. (read-only)
#property(nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer
The navigation controller installs this gesture recognizer on its view
and uses it to pop the topmost view controller off the navigation
stack. You can use this property to retrieve the gesture recognizer
and tie it to the behavior of other gesture recognizers in your user
interface. When tying your gesture recognizers together, make sure
they recognize their gestures simultaneously to ensure that your
gesture recognizers are given a chance to handle the event.
So you will have to implement your own UIGestureRecognizer, and tie its behavior to the interactivePopGestureRecognizer of your UIViewController.
Edit :
Here is a solution I built. You can implement your own transition conforming to the UIViewControllerAnimatedTransitioning delegate. This solution works, but has not been thoroughly tested.
You will get an interactive sliding transition to pop your ViewControllers. You can slide to right from anywhere in the view.
Known issue : if you start the pan and stop before half the width of the view, the transition is canceled (expected behavior). During this process, the views reset to their original frames. Their is a visual glitch during this animation.
The classes of the example are the following :
UINavigationController > ViewController > SecondViewController
CustomPopTransition.h :
#import <Foundation/Foundation.h>
#interface CustomPopTransition : NSObject <UIViewControllerAnimatedTransitioning>
#end
CustomPopTransition.m :
#import "CustomPopTransition.h"
#import "SecondViewController.h"
#import "ViewController.h"
#implementation CustomPopTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.3;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
SecondViewController *fromViewController = (SecondViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
ViewController *toViewController = (ViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toViewController.view];
[containerView bringSubviewToFront:fromViewController.view];
// Setup the initial view states
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
[UIView animateWithDuration:0.3 animations:^{
fromViewController.view.frame = CGRectMake(toViewController.view.frame.size.width, fromViewController.view.frame.origin.y, fromViewController.view.frame.size.width, fromViewController.view.frame.size.height);
} completion:^(BOOL finished) {
// Declare that we've finished
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
#end
SecondViewController.h :
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController <UINavigationControllerDelegate>
#end
SecondViewController.m :
#import "SecondViewController.h"
#import "ViewController.h"
#import "CustomPopTransition.h"
#interface SecondViewController ()
#property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactivePopTransition;
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.delegate = self;
UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePopRecognizer:)];
[self.view addGestureRecognizer:popRecognizer];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// Stop being the navigation controller's delegate
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
// Check if we're transitioning from this view controller to a DSLSecondViewController
if (fromVC == self && [toVC isKindOfClass:[ViewController class]]) {
return [[CustomPopTransition alloc] init];
}
else {
return nil;
}
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
// Check if this is for our custom transition
if ([animationController isKindOfClass:[CustomPopTransition class]]) {
return self.interactivePopTransition;
}
else {
return nil;
}
}
- (void)handlePopRecognizer:(UIPanGestureRecognizer*)recognizer {
// Calculate how far the user has dragged across the view
CGFloat progress = [recognizer translationInView:self.view].x / (self.view.bounds.size.width * 1.0);
progress = MIN(1.0, MAX(0.0, progress));
if (recognizer.state == UIGestureRecognizerStateBegan) {
NSLog(#"began");
// Create a interactive transition and pop the view controller
self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
[self.navigationController popViewControllerAnimated:YES];
}
else if (recognizer.state == UIGestureRecognizerStateChanged) {
NSLog(#"changed");
// Update the interactive transition's progress
[self.interactivePopTransition updateInteractiveTransition:progress];
}
else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) {
NSLog(#"ended/cancelled");
// Finish or cancel the interactive transition
if (progress > 0.5) {
[self.interactivePopTransition finishInteractiveTransition];
}
else {
[self.interactivePopTransition cancelInteractiveTransition];
}
self.interactivePopTransition = nil;
}
}
#end
Create a pan gesture recogniser and move the interactive pop gesture recogniser's targets across.
Add your recogniser to the pushed view controller's viewDidLoad and voila!
let popGestureRecognizer = self.navigationController!.interactivePopGestureRecognizer!
if let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
let gestureRecognizer = UIPanGestureRecognizer()
gestureRecognizer.setValue(targets, forKey: "targets")
self.view.addGestureRecognizer(gestureRecognizer)
}
Here's a Swift version of Spynet's answer, with a few modifications. Firstly, I've defined a linear curve for the UIView animation. Secondly, I've added a semi-transparent black background to the view underneath for a better effect. Thirdly, I've subclassed a UINavigationController. This allows the transition to be applied to any "Pop" transition within the UINavigationController. Here's the code:
CustomPopTransition.swift
import UIKit
class CustomPopTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
else {
return
}
let containerView = transitionContext.containerView
containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
// Setup the initial view states
toViewController.view.frame = CGRect(x: -100, y: toViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
let dimmingView = UIView(frame: CGRect(x: 0,y: 0, width: toViewController.view.frame.width, height: toViewController.view.frame.height))
dimmingView.backgroundColor = UIColor.black
dimmingView.alpha = 0.5
toViewController.view.addSubview(dimmingView)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: UIView.AnimationOptions.curveLinear,
animations: {
dimmingView.alpha = 0
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
fromViewController.view.frame = CGRect(x: toViewController.view.frame.size.width, y: fromViewController.view.frame.origin.y, width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
},
completion: { finished in
dimmingView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}
PoppingNavigationController.swift
import UIKit
class PoppingNavigationController : UINavigationController, UINavigationControllerDelegate {
var interactivePopTransition: UIPercentDrivenInteractiveTransition!
override func viewDidLoad() {
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
addPanGesture(viewController: viewController)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if (operation == .pop) {
return CustomPopTransition()
}
else {
return nil
}
}
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if animationController.isKind(of: CustomPopTransition.self) {
return interactivePopTransition
}
else {
return nil
}
}
func addPanGesture(viewController: UIViewController) {
let popRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecognizer(recognizer:)))
viewController.view.addGestureRecognizer(popRecognizer)
}
#objc
func handlePanRecognizer(recognizer: UIPanGestureRecognizer) {
// Calculate how far the user has dragged across the view
var progress = recognizer.translation(in: self.view).x / self.view.bounds.size.width
progress = min(1, max(0, progress))
if (recognizer.state == .began) {
// Create a interactive transition and pop the view controller
self.interactivePopTransition = UIPercentDrivenInteractiveTransition()
self.popViewController(animated: true)
}
else if (recognizer.state == .changed) {
// Update the interactive transition's progress
interactivePopTransition.update(progress)
}
else if (recognizer.state == .ended || recognizer.state == .cancelled) {
// Finish or cancel the interactive transition
if (progress > 0.5) {
interactivePopTransition.finish()
}
else {
interactivePopTransition.cancel()
}
interactivePopTransition = nil
}
}
}
Example of the result:
There really is no need to roll your own solution for this, sub-classing UINavigationController and referencing the built-in gesture works just fine as explained here.
The same solution in Swift:
public final class MyNavigationController: UINavigationController {
public override func viewDidLoad() {
super.viewDidLoad()
self.view.addGestureRecognizer(self.fullScreenPanGestureRecognizer)
}
private lazy var fullScreenPanGestureRecognizer: UIPanGestureRecognizer = {
let gestureRecognizer = UIPanGestureRecognizer()
if let cachedInteractionController = self.value(forKey: "_cachedInteractionController") as? NSObject {
let string = "handleNavigationTransition:"
let selector = Selector(string)
if cachedInteractionController.responds(to: selector) {
gestureRecognizer.addTarget(cachedInteractionController, action: selector)
}
}
return gestureRecognizer
}()
}
If you do this, also implement the following UINavigationControllerDelegate function to avoid strange behaviour at the root view controller:
public func navigationController(_: UINavigationController,
didShow _: UIViewController, animated _: Bool) {
self.fullScreenPanGestureRecognizer.isEnabled = self.viewControllers.count > 1
}
Subclassing the UINavigationController you can add a UISwipeGestureRecognizer to trigger the pop action:
.h file:
#import <UIKit/UIKit.h>
#interface CNavigationController : UINavigationController
#end
.m file:
#import "CNavigationController.h"
#interface CNavigationController ()<UIGestureRecognizerDelegate, UINavigationControllerDelegate>
#property (nonatomic, retain) UISwipeGestureRecognizer *swipeGesture;
#end
#implementation CNavigationController
#pragma mark - View cycles
- (void)viewDidLoad {
[super viewDidLoad];
__weak CNavigationController *weakSelf = self;
self.delegate = weakSelf;
self.swipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(gestureFired:)];
[self.view addGestureRecognizer:self.swipeGesture]; }
#pragma mark - gesture method
-(void)gestureFired:(UISwipeGestureRecognizer *)gesture {
if (gesture.direction == UISwipeGestureRecognizerDirectionRight)
{
[self popViewControllerAnimated:YES];
} }
#pragma mark - UINavigation Controller delegate
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.swipeGesture.enabled = NO;
[super pushViewController:viewController animated:animated]; }
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate {
self.swipeGesture.enabled = YES; }
#end

Present popover from cells accessory button?

I'm having a hard time presenting a popover correctly from the accessoryButton of a tableviewCell.
The reason I'm not using accessory view is because the cell is in edit mode and I couldn't display both the green plus sign + custom accessory view.. Maybe I overlooked something on that front?
Currently my popover shows correctly, but that's only the case for this configuration since I set a static distance from the origin... Any Ideas how to solve this?
Code:
-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (![self duplicateDayContent]) {
duplicateDayContent = [[self storyboard]instantiateViewControllerWithIdentifier:#"CopyDay"];
[duplicateDayContent setDelegate:self];
duplicateDayPopover = [[UIPopoverController alloc]initWithContentViewController:duplicateDayContent];
duplicateDayPopover.popoverContentSize = CGSizeMake(320, 600);
}
CGRect rect = CGRectMake(cell.bounds.origin.x+800, cell.bounds.origin.y+10, 50, 30);
[duplicateDayPopover presentPopoverFromRect:rect inView:cell permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];
}
This works quite nicely for a cell with accessoryType .detailDisclosureButton:
if let ppc = vc.popoverPresentationController, let cell = tableView.cellForRow(at: indexPath) {
ppc.sourceView = cell
ppc.sourceRect = CGRect(x: cell.bounds.width - 58, y: cell.bounds.height/2 - 11, width: 22, height: 22)
vc.modalPresentationStyle = .popover
}
present(vc, animated: true, completion: nil)
You would typically do this in tableView(_ accessoryButtonTappedForRowWith indexPath:)
You can also test the position of the calculated frame with a marker:
let marker = UIView(frame: ppc.sourceRect)
marker.backgroundColor = UIColor.red.withAlphaComponent(0.2)
cell.addSubview(marker)
this code from this thread helped me: How to correctly present a popover from a UITableViewCell with UIPopoverArrowDirectionRight or UIPopoverArrowDirectionLeft
thanks to rachels hint
UIView *accessoryView = cell.accessoryView; // finds custom accesoryView (cell.accesoryView)
if (accessoryView == nil) {
UIView *cellContentView = nil;
for (UIView *accView in [cell subviews]) {
if ([accView isKindOfClass:[UIButton class]]) {
accessoryView = accView; // find generated accesoryView (UIButton)
break;
} else if ([accView isKindOfClass:NSClassFromString(#"UITableViewCellContentView")]) {
// find generated UITableViewCellContentView
cellContentView = accView;
}
}
// if the UIButton doesn't exists, find cell contet view (UITableViewCellContentView)
if (accessoryView == nil) {
accessoryView = cellContentView;
}
// if the cell contet view doesn't exists, use cell view
if (accessoryView == nil) {
accessoryView = cell;
}
}
In swift, this has worked for me:
Create a popover presentation segue, then use prepareForSegue to configure the UIPopoverPresentationController of the destination view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == Storyboard.providerInfoSegue) {
if let vc = segue.destinationViewController.contentViewController as? /*DestinationViewControllerType*/ {
//Configure view controllers here
if let popOverPresentationController : UIPopoverPresentationController = vc.popoverPresentationController {
if let cell = tableView.cellForRowAtIndexPath(selectedAccessoryIndexPath) {
var accessoryView: UIButton!
for accView in cell.subviews {
if accView is UIButton {
accessoryView = accView as? UIButton
break
}
}
popOverPresentationController.delegate = self
popOverPresentationController.sourceView = cell
popOverPresentationController.sourceRect = accessoryView.frame
popOverPresentationController.permittedArrowDirections = UIPopoverArrowDirection.Right
}
}
}
}
}

Resources