Snapchat-like swipe navigation between views in Xcode 6 and Swift)
Solution 1
The short version is to use a container view controller with a scrollview inside the controller. You then create separate view controllers for each screen you want in the application, and make those view controllers' parent the container view controller.
A github repo with sample code can be found, here.
Solution 2
You need a pageviewcontroller. This was originally for showing tutorials and stuff but you can put view controllers in there as well. There are tons of tutorials out there and essentially you have to apply a little bit of logic to tell the program what view controller to show it next.
This is a pretty advanced example, but it might be of help to you:
https://github.com/cwRichardKim/RKSwipeBetweenViewControllers
Solution 3
I'm not fond of the version given by lbrendanl because it does not use constraints. We can not custom it like we want. Here is the same version but with constraints :
scrollView is an IBOutlet pined to the controller with 4 constraints with a constant to 0 at each sides to the controller's view.
contentView is also an IBOutlet added as subview of scrollView pined to the scrollView with 4 constraints with a constant to 0 at each sides. It also has an equal height constraint and a width equal constraint. The width equal constraint is removed at runtime and only serves to calm down IB. This view represents the contentView of the scrollView.
UPDATE iOS 9
func setupDetailViewControllers() {
var previousController: UIViewController?
for controller in self.controllers {
addChildViewController(controller)
addControllerInContentView(controller, previousController: previousController)
controller.didMoveToParentViewController(self)
previousController = controller
}
}
func addControllerInContentView(controller: UIViewController, previousController: UIViewController?) {
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
// top
controller.view.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
// bottom
controller.view.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
// trailing
trailingContentViewConstraint?.active = false
trailingContentViewConstraint = controller.view.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
trailingContentViewConstraint?.active = true
// leading
let leadingAnchor = previousController?.view.trailingAnchor ?? contentView.leadingAnchor
controller.view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
// width
controller.view.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
}
PREVIOUS ANSWER
class ContainerViewController: UIViewController {
@IBOutlet var scrollView: UIScrollView!
@IBOutlet var contentView: UIView!
// A strong reference to the width contraint of the contentView
var contentViewConstraint: NSLayoutConstraint!
// A computed version of this reference
var computedContentViewConstraint: NSLayoutConstraint {
return NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: .Width, multiplier: CGFloat(controllers.count + 1), constant: 0)
}
// The list of controllers currently present in the scrollView
var controllers = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
initScrollView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initScrollView(){
contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
contentViewConstraint = computedContentViewConstraint
view.addConstraint(contentViewConstraint)
// Adding all the controllers you want in the scrollView
let controller1 = storyboard!.instantiateViewControllerWithIdentifier("AStoryboardID") as! AnUIControllerViewSubclass
addToScrollViewNewController(controller)
let controller2 = storyboard!.instantiateViewControllerWithIdentifier("AnotherStoryboardID") as! AnotherUIControllerViewSubclass
addToScrollViewNewController(controller2)
}
// The main method, adds the controller in the scrollView at the left of the previous controller added
func addToScrollViewNewController(controller: UIViewController) {
self.addChildViewController(controller)
contentView.addSubview(controller.view)
controller.view.setTranslatesAutoresizingMaskIntoConstraints(false)
// Setting all the constraints
let bottomConstraint = NSLayoutConstraint(item: contentView, attribute: .Bottom, relatedBy: .Equal, toItem: controller.view, attribute: .Bottom, multiplier: 1.0, constant: 0)
let topConstraint = NSLayoutConstraint(item: contentView, attribute: .Top, relatedBy: .Equal, toItem: controller.view, attribute: .Top, multiplier: 1.0, constant: 0)
let widthConstraint = NSLayoutConstraint(item: controller.view, attribute: .Width, relatedBy: .Equal, toItem: scrollView, attribute: .Width, multiplier: 1.0, constant: 0)
var trailingConstraint: NSLayoutConstraint!
if controllers.isEmpty {
// Since it's the first one, the trailing constraint is from the controller view to the contentView
trailingConstraint = NSLayoutConstraint(item: contentView, attribute: .Trailing, relatedBy: .Equal, toItem: controller.view, attribute: .Trailing, multiplier: 1.0, constant: 0)
}
else {
trailingConstraint = NSLayoutConstraint(item: controllers.last!.view, attribute: .Leading, relatedBy: .Equal, toItem: controller.view, attribute: .Trailing, multiplier: 1.0, constant: 0)
}
// Setting the new width constraint of the contentView
view.removeConstraint(contentViewConstraint)
contentViewConstraint = computedContentViewConstraint
// Adding all the constraints to the view hierarchy
view.addConstraint(contentViewConstraint)
contentView.addConstraints([bottomConstraint, topConstraint, trailingConstraint])
scrollView.addConstraints([widthConstraint])
controller.didMoveToParentViewController(self)
// Finally adding the controller in the list of controllers
controllers.append(controller)
}
}
I've used the lbrendanl's version in the past. Now I prefer this one. Let me know what you think of it.
Solution 4
I suggest using UIPageViewController and hiding the dots bar by deleting these methods:
presentationCountForPageViewController
presentationIndexForPageViewController
Here is a good tutorial:
https://www.youtube.com/watch?v=8bltsDG2ENQ
Here is a great repo for this:
https://github.com/goktugyil/EZSwipeController
Solution 5
Paged Scroll View, not a PageViewController in the case of Snapchat.
maxster256
Updated on September 15, 2020Comments
-
maxster256 over 3 years
I've been trying to implement swipe navigation between View Controllers in my app using the Swipe Gesture Recognizer and embeded Navigation Controller, but it doesn't look even close to the Snapchat's nav.
What would be the most efficient and appropiate way to implement such functionality?
I'm quite a newbie to Swift and programming really, and I would appreciate every helpful comment.
-
Steve Sahayadarlin almost 10 yearsIf this helped you, please click the checkmark. Thanks!
-
cud_programmer over 9 yearsTutorial helped me - thanks. However, your code no longer works (Xcode 6 beta 6, running iOS 8 beta 5) - compiles fine, but view controllers aren't instantiated properly. Instead I instantiated them directly from their nib using
var AVc: AViewController = AViewController(nibName: "AViewController", bundle: nil)
. Hope this helps someone! -
lbrendanl over 9 yearsAwesome, thank you. I haven't updated for beta6 yet, but I will add your change when I do.
-
mircobabini over 9 yearsEXACTLY what I was looking for, thank you so much dude. Thanks. Edit: even if he was asking for swift code!
-
Jake Weso over 9 years??? What does this do differently from Brendans code? Its the same thing. What do you mean directly from their nib? This isn't working for me and I don't know how to fix it... reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "CViewController" nib but the view outlet was not set.'
-
Jake Weso over 9 yearsHi, i finally got it to work... your tutorial is providing incorrect steps... but now i have a problem. I want to have 7 pages instead of 3, and I added code for 7 controllers, but only the first 3 pages work. The other 4 are blank. Any ideas??
-
lbrendanl over 9 yearsDead link now, my apologies, I had to take this blog post down. Cannot edit/remove the answer unfortunately, but the github repo is still live if you need code samples.
-
user3498976 almost 9 years@lbrendani is there a way to programmatically scroll to a certain view by pressing a 'UIButton' ? I have tried with a protocol with delegate being set in 'ContainerViewController' 'viewDidLoad' but it keeps showing that the 'scrollView' is nil ... Thanks!
-
lbrendanl almost 9 years@user3498976, check out my last comment here: github.com/lbrendanl/SwiftSwipeView/issues/7. That should answer your question.
-
user3498976 almost 9 years@lbrendanl thank you!! Anyone out there reading this, the "issues" section is incredibly helpful (as is the entire example/tutorial)
-
applemavs over 8 yearsI think this should get more attention. This helped a lot. Thank you!
-
Nitin Nain about 8 yearsI used this version; it works, but it isn't plug and play. I had to work a lot on adjusting the constraints.. they seemed to be a few pixels off all the time.
-
Zorayr over 7 yearsThe only caveat here is that
UIPagesViewController
doesn't let you know transition progress without looking into private subviews, which means you can't have progress-based transitions in your view. -
L A over 7 yearsWhat were the benefits in using a UICollectionView?