Card Slide Gallery in UIViewController - swift

Is there a way to implement a card sliding gallery in a UIViewController? The gallery is not supposed to have images but buttons. I'm thinking the app "Bumble" for example when it comes to their subscription process. You can swipe through 2 cards to choose your subscription model. This has to be implemented in a UIViewController .. Any chance to get this done?

You can try using UICollectionView to emulate how bumble cardview works
make the scroll direction to horizontal and disable the scroll indicator
and then, add UICollectionViewDelegateFlowLayout protocol and use these two bad boys
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
}
what the two of them do is detect when the scroll gesture is decelerating and end of the scroll gesture. You can try to engineered the movement
here's an example how I implement it
private lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
//layout.itemSize = CGSize(width: 330, height: 150)
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 70, width: UIScreen.main.bounds.width, height: 150),collectionViewLayout: layout)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell2")
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
delegate :
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.collectionView.scrollToNearestVisibleCollectionViewCell()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.collectionView.scrollToNearestVisibleCollectionViewCell()
}
}

Related

How filter and forward gestures on UIView who covers another UIView with the same size and position?

Description is simple. I have 2 subviews (topView and bottomView UIViews) into main view and both cover whole screen. bottomView is bellow topView.
For topView I added double-tap gesture using UITapGestureRecognizer and for bottomView I added pinch gesture using UIPinchGestureRecognizer.
The goal is: When I made pinch gesture (on the topView) I want to forward that pinch gesture to the bottomView. Double-tap gesture should be executed on the topView. In other words, how to forward only pinch gesture to the bottomView and every other gesture should be executed by topView.
Note: Keep in mind that both UIViews cover whole screen.
I provided code for the start.
I tried to solve this problem overriding UIView's methods hitTest and point, but no luck.
import UIKit
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let bottomView: UIView = {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: width, height: height)
view.backgroundColor = .yellow
return view
}()
let topView: UIView = {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: width, height: height)
view.backgroundColor = .orange
return view
}()
let doubleTouch = UITapGestureRecognizer(target: self, action: #selector(doubleTap(sender:)))
doubleTouch.numberOfTapsRequired = 2
topView.addGestureRecognizer(doubleTouch)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(pinch(sender:)))
bottomView.addGestureRecognizer(pinch)
view.addSubview(bottomView)
view.addSubview(topView)
}
#objc func doubleTap(sender: UITapGestureRecognizer) {
print("Double Tap")
}
#objc func pinch(sender: UIPinchGestureRecognizer) {
print("Pinch")
}
}

UIScrollView not zooming: What am I missing?

I can scroll, but not zoom. I've seen a lot of identical looking code while searching, but still there's no zoom.
The pinchGestureRecognizer is not nil.
viewForZoomingInScrollView(scrollView: UIScrollView) is not being called.
scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) reports scale = 1.0.
What am I missing here?
(We need to do this programmatically rather than in IB).
import UIKit
class ScrollViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(image: UIImage(named: "image.png"))
scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = UIColor.black
scrollView.contentSize = imageView.bounds.size
scrollView.contentOffset = CGPoint(x: 1000, y: 450)
scrollView.addSubview(imageView)
view.addSubview(scrollView)
scrollView.delegate = self
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 4.0
scrollView.zoomScale = 1.0
}
//delegate
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Did you convert your codes from an older version of Swift? Because the delegate function for returning the view for zooming is now
func viewForZooming(in scrollView: UIScrollView) -> UIView?

Making a collection view scroll out side menu swift

I've looked all over the place for an answer and can't find one. I'm creating an app that has a menu that pops out from the left side using a button and animations. The menu itself is a collection view. Currently when the menu button is pressed, the entire collection view pops out from the left side. I want to do this same thing, but I want the collection view to respond to a swipe from left to right. Please keep in mind I don't want the individual things INSIDE the collection view to move, but the collection view itself as a whole menu. How would I go about doing this?
Good evening!
If i understand your question correctly. You simply have a button which you want to use to animate a slide in of a UICollectionView. Here is what I would do:
Create a new collection view class: (if you want a different frame size of the collectionView remember to change it in both animations as well as the initial setup in showSlideCollectionView
import UIKit
class CollectionViewLauncher : NSObject {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
// reuseidentifier for the cells in the collectionview
let cellId = "cellId"
var viewController: UIViewController?
func showSlideCollectionView() {
if let window = UIApplication.shared.keyWindow {
window.addSubview(collectionView)
let width: CGFloat = window.frame.width * 0.55
//this is the original frame for the collectionview (so it is off screen by default)
collectionView.frame = CGRect(x: -window.frame.width, y: 0, width: width, height: window.frame.height)
//this animates the collectionview on screen
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.collectionView.frame = CGRect(x: 0, y: 0, width: width, height: window.frame.height)
}, completion: nil)
}
}
func hideSlideCollectionView() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
//sliding the collectionview off screen
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: window.frame.width, y: 0, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}, completion: nil)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
//register your own cell class type here. The code wont work unless you create a cell class of type UICollectionViewCell and customise it there and then register the cell here. (you can also create a nib file and register that.
collectionView.register(YourCell.self, forCellWithReuseIdentifier: cellId)
}
}
extension CollectionViewLauncher: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! YourCell //your cell class
//setup what you need in the cells
return cell
}
//add additional UICollectionView Data source methods if you need further customisation of the collectionview.
}
Register your cell class in the overrode init() method inside the CollectionViewLauncher.
In the your ViewController do the following.
lazy var collectionViewLauncher : CollectionViewLauncher = {
let launcher = CollectionViewLauncher()
launcher.viewController = self
return launcher
}()
//in your button action you call the following to animate the collection view to appear.
collectionViewLauncher.showSlideCollectionView()
//when you want it to dissapear call the following to animate it away (perhaps in a UITapGestureRecognizer or something).
collectionViewLauncher.hideSlideCollectionView()
//example button action
#objc func yourButtonsAction(sender: UIButton) {
collectionViewLauncher.showSlideCollectionView()
}
Edit:
If you have data that you need to pass to this collection view from your view controller i would recommend implementing it directly in the same class as your view controller instead. (Just do exactly the same but put the stuff in the init() method in viewDidLoad instead and the rest within the ViewController class).
If you want it to be slidable using a drag gesture instead. You should use the approach in the edit here and then use a pan gesture to move it instead of the show/hideSlideCollectionView methods.
I hope this helps.

UIViewController not adjusting correctly in UIScrollView for all iPhones

I am trying to replicate a Tinder like menu, with the 3 UIViewControllers in a UIScrollView and a custom menu tab on the top, with a button for each UIViewController. I am facing an interesting problem where the UIViewControllers views fit perfectly in the scrollView.frame, but only for iPhone 8. In contrast, for iPhone SE, it leaves a white margin and for iPhone 8+, it seems to overlap the views within the scrollView.view. Could someone explain why this is happening and how I can fix it?
Here's my code where I'm adjusting setting up my UIViewControllers in my UIScrollView:
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var navigationView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setUpHorizontalScrollViews()
}
func setUpHorizontalScrollViews(){
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
let scrollWidth = 3 * view.width
let scrollHeight = view.height
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
scrollView.contentOffset.x = view.x
if let messagesView = self.storyboard?.instantiateViewController(withIdentifier: "MessagesVC") as UIViewController! {
self.addChildViewController(messagesView)
self.scrollView.addSubview(messagesView.view)
messagesView.didMove(toParentViewController: self)
messagesView.view.frame = CGRect(x: 0,
y: 0,
width: view.width,
height: view.height
)
}
if let friendsView = self.storyboard?.instantiateViewController(withIdentifier: "FriendsVC") as UIViewController! {
self.addChildViewController(friendsView)
self.scrollView.addSubview(friendsView.view)
friendsView.didMove(toParentViewController: self)
friendsView.view.frame = CGRect(x: view.width,
y: 0,
width: view.width,
height: view.height
)
}
if let settingsView = self.storyboard?.instantiateViewController(withIdentifier: "SettingsVC") as UIViewController! {
self.addChildViewController(settingsView)
self.scrollView.addSubview(settingsView.view)
settingsView.didMove(toParentViewController: self)
settingsView.view.frame = CGRect(x: 2 * view.width,
y: 0,
width: view.width,
height: view.height
)
}
// offset to the second view
self.scrollView.contentOffset.x = view.width
}
override var shouldAutorotate: Bool {
return false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is what my Setup looks like. the top is the MainViewController, containing the UIScrollView and on bottom are the 3 viewcontrollers that are supposed to go into the scrollView.
This is what I want it to look like, and the way it sets up in iPhone 8:
This is what it looks like on iPhone SE and where my problem is:
The problem is that since you are calling the setUpHorizontalScrollViews method in the viewDidLoad(), the UIViewController has yet to layout the subview, and also calculate their final size.
It is working in an iPhone 8 because most provably it has the same screen size you used in the interface builder.
Solution 1
In order to solve the problem, you can move your code to the viewDidAppear() method. However, this will cause an ugly effect once you open the UIViewController (unless you add a full screen loading).
Solution 2
Add view.layourIfNeeded() like this:
override func viewDidLoad() {
super.viewDidLoad()
view.layourIfNeeded()
setUpHorizontalScrollViews()
}

UICollectionView ScrollTo Crashing

I am attempting to have my UICollectionView in my custom UIView, be able to scroll to a page when a button is pressed in the main VC. Unfortunately everytime I call the method to do it in the MainVC, it crashes, # to scrollToItem .
here is my MainVC.
let mainView = MainViewsHome()
In the ViewWillAppear:
/** Setting up the bottom half **/
mainView.frame = CGRect(x:self.view.frame.width * 0, y:self.view.frame.height / 6.2, width:self.view.frame.width,height:self.view.frame.height / 1.1925)
mainView.backgroundColor = UIColor.clear
self.view.addSubview(mainView)
&& My custom UIView
class MainViewsHome: UIView, UIGestureRecognizerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var cellId = "Cell"
var collectionView : UICollectionView!
override init(frame: CGRect) {
super.init(frame: CGRect(x:0, y:0, width:UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 1.3))
/**Creation of the View **/
let flowLayout : UICollectionViewFlowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
flowLayout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: CGRect(x:self.frame.width * 0,y:self.frame.height * 0,width:self.frame.width,height: self.frame.height), collectionViewLayout: flowLayout)
collectionView.register(uploadGenreSelectionCVC.self, forCellWithReuseIdentifier: cellId)
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.purple
self.addSubview(collectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveToPage() {
print("we are moving to page")
self.collectionView.scrollToItem(at: 2 as IndexPath, at: UICollectionViewScrollPosition.right, animated: true)
}
&& Then in my MainVC I am calling: mainView.moveToPage() Where am I going wrong with my understanding?
Problem is with 2 as IndexPath, IndexPath is not Integer so you need to make the object of IndexPath using its init init(item:section:). and after that scroll collectionView to specific item this way.
func moveToPage() {
let indexPath = IndexPath(item: 2, section: 0) //Change section from 0 to other if you are having multiple sections
self.collectionView.scrollToItem(at: indexPath, at: .right, animated: true)
}
Note: CollectionView's items start at 0 index so if you want to scroll to 2nd item then make indexPath using IndexPath(item: 1, section: 0) because 2nd will scroll at 3rd cell of collectionView.