Advertisement
srk72

Small bottom sheet iOS

Apr 24th, 2023
1,185
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 12.20 KB | None | 0 0
  1. //
  2. //  BottomSheetVC.swift
  3. //  BeFriends
  4. //
  5. //  Created by Ramesh Sanghar on 17/04/23.
  6. //
  7.  
  8. import UIKit
  9.  
  10. class ShareBottomSheetVC: UIViewController {
  11.    
  12.     // 1
  13.     lazy var containerView: UIView = {
  14.         let view = UIView()
  15.         view.backgroundColor = .white
  16.         view.layer.cornerRadius = 16
  17.         view.clipsToBounds = true
  18.         return view
  19.     }()
  20.    
  21.     // define lazy views
  22.     lazy var titleLabel: UILabel = {
  23.         let label = UILabel()
  24.         label.textColor = .lightGray
  25.         label.translatesAutoresizingMaskIntoConstraints = false
  26.         label.text = "GET IN TOUCH"
  27.         label.font = .systemFont(ofSize: 15, weight: .light)
  28.         return label
  29.     }()
  30.    
  31.     lazy var lineView: UIView = {
  32.         let line = UIView()
  33.         line.translatesAutoresizingMaskIntoConstraints = false
  34.         line.backgroundColor = StaticValues.TINT_COLOR
  35.         return line
  36.     }()
  37.    
  38.     var shareView: [UIView] = []
  39.    
  40.     lazy var contentStackView: UIStackView = {
  41.         let stackView = UIStackView()
  42.         stackView.axis = .horizontal
  43.         stackView.spacing = 20
  44.         stackView.alignment = .center
  45.         stackView.distribution = .equalSpacing
  46.         return stackView
  47.     }()
  48.    
  49.     // 2
  50.     let maxDimmedAlpha: CGFloat = 0.6
  51.     lazy var dimmedView: UIView = {
  52.         let view = UIView()
  53.         view.backgroundColor = .black
  54.         view.alpha = maxDimmedAlpha
  55.         return view
  56.     }()
  57.    
  58.     let defaultHeight: CGFloat = 300
  59.     let dismissibleHeight: CGFloat = 200
  60.     let maximumContainerHeight: CGFloat = UIScreen.main.bounds.height - 64
  61.     // keep updated with new height
  62.     var currentContainerHeight: CGFloat = 300
  63.    
  64.    
  65.     // 3. Dynamic container constraint
  66.     var containerViewHeightConstraint: NSLayoutConstraint?
  67.     var containerViewBottomConstraint: NSLayoutConstraint?
  68.    
  69.     var isLargeSizeEnabled = true
  70.    
  71.     let shareTitles = ["WhatsApp","Instagram","Write to us","Visit Us"]
  72.     let shareImages = ["message","camera","envelope","globe"]
  73.    
  74.     override func viewDidLoad() {
  75.         super.viewDidLoad()
  76.        
  77.         let imageSize: CGFloat = 30
  78.        
  79.         for i in 0...3 {
  80.            
  81.             let sView = UIView()
  82.             sView.backgroundColor = .clear
  83.             sView.translatesAutoresizingMaskIntoConstraints = false
  84.             sView.heightAnchor.constraint(equalToConstant: 70).isActive = true
  85.             sView.widthAnchor.constraint(equalToConstant: 70).isActive = true
  86.            
  87.             let imageView = UIImageView()
  88.             imageView.translatesAutoresizingMaskIntoConstraints = false
  89.             imageView.image = UIImage(systemName: shareImages[i])?.withAlignmentRectInsets(UIEdgeInsets(top: 5, left: -5, bottom: -5, right: 5))
  90.             imageView.tintColor = .white
  91.             imageView.contentMode = .scaleAspectFit
  92.             imageView.backgroundColor = StaticValues.TINT_COLOR
  93. //            imageView.image?.inse = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
  94.            
  95.             sView.addSubview(imageView)
  96.            
  97.            
  98.             imageView.topAnchor.constraint(equalTo: sView.topAnchor, constant: 5).isActive = true
  99.             imageView.centerXAnchor.constraint(equalTo: sView.centerXAnchor).isActive = true
  100.            
  101.            
  102.             let nameLbl = UILabel()
  103.             nameLbl.backgroundColor = .clear
  104.             nameLbl.translatesAutoresizingMaskIntoConstraints = false
  105.             nameLbl.text = shareTitles[i]
  106.             nameLbl.textColor = StaticValues.TINT_COLOR
  107.             nameLbl.textAlignment = .center
  108.             nameLbl.font = UIFont.systemFont(ofSize: 12, weight: .medium)
  109.            
  110.            
  111.             sView.addSubview(nameLbl)
  112.            
  113.             nameLbl.heightAnchor.constraint(equalToConstant: 24).isActive = true
  114.             nameLbl.leadingAnchor.constraint(equalTo: sView.leadingAnchor).isActive = true
  115.             nameLbl.trailingAnchor.constraint(equalTo: sView.trailingAnchor).isActive = true
  116.             nameLbl.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5).isActive = true
  117.            
  118.             imageView.cornerRadius = imageView.halfHeight
  119.            
  120.             contentStackView.addArrangedSubview(sView)
  121.            
  122.             shareView.append(sView)
  123.            
  124.             imageView.heightAnchor.constraint(equalToConstant: imageSize).isActive = true
  125.             imageView.widthAnchor.constraint(equalToConstant: imageSize).isActive = true
  126.            
  127.             imageView.clipsToBounds = true
  128.            
  129.             imageView.layer.cornerRadius = imageSize/2
  130.             imageView.layer.masksToBounds = true
  131.            
  132.         }
  133.        
  134.         setupView()
  135.         setupConstraints()
  136.         setupPanGesture()
  137.     }
  138.    
  139.     override func viewDidAppear(_ animated: Bool) {
  140.         super.viewDidAppear(animated)
  141.         animateShowDimmedView()
  142.         animatePresentContainer()
  143.     }
  144.    
  145.     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  146.        
  147.         let touch = touches.first
  148.        
  149.         if touch?.view == dimmedView {
  150.             animateDismissView()
  151.         }
  152.     }
  153.    
  154.     func setupView() {
  155.         view.backgroundColor = .clear
  156.     }
  157.    
  158.     @objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
  159.         let translation = gesture.translation(in: view)
  160.         // Drag to top will be minus value and vice versa
  161.       //  print("Pan gesture y offset: \(translation.y)")
  162.  
  163.         // Get drag direction
  164.         let isDraggingDown = translation.y > 0
  165.       //  print("Dragging direction: \(isDraggingDown ? "going down" : "going up")")
  166.  
  167.         // New height is based on value of dragging plus current container height
  168.         let newHeight = currentContainerHeight - translation.y
  169.  
  170.         // Handle based on gesture state
  171.         switch gesture.state {
  172.         case .changed:
  173.            
  174.             // This state will occur when user is dragging
  175.            if newHeight < maximumContainerHeight {
  176.                 // Keep updating the height constraint
  177.                 containerViewHeightConstraint?.constant = newHeight
  178.                 // refresh layout
  179.                 view.layoutIfNeeded()
  180.             }
  181.         case .ended:
  182.             // This happens when user stop drag,
  183.             // so we will get the last height of container
  184.             // Condition 1: If new height is below min, dismiss controller
  185.             if newHeight < dismissibleHeight {
  186.                 self.animateDismissView()
  187.             }
  188.             else {
  189.                 self.animateContainerHeight(defaultHeight)
  190.             }
  191.            
  192.         default:
  193.             break
  194.         }
  195.     }
  196.    
  197.     func animateContainerHeight(_ height: CGFloat) {
  198.         UIView.animate(withDuration: 0.4) {
  199.             // Update container height
  200.             self.containerViewHeightConstraint?.constant = height
  201.             // Call this to trigger refresh constraint
  202.             self.view.layoutIfNeeded()
  203.         }
  204.         // Save current height
  205.         currentContainerHeight = height
  206.     }
  207.    
  208.     func setupPanGesture() {
  209.         // add pan gesture recognizer to the view controller's view (the whole screen)
  210.         let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture(gesture:)))
  211.         // change to false to immediately listen on gesture movement
  212.         panGesture.delaysTouchesBegan = false
  213.         panGesture.delaysTouchesEnded = false
  214.         view.addGestureRecognizer(panGesture)
  215.     }
  216.    
  217.     func animateShowDimmedView() {
  218.         dimmedView.alpha = 0
  219.         UIView.animate(withDuration: 0.4) {
  220.             self.dimmedView.alpha = self.maxDimmedAlpha
  221.         }
  222.     }
  223.    
  224.     func animatePresentContainer() {
  225.         // Update bottom constraint in animation block
  226.         UIView.animate(withDuration: 0.3) {
  227.             self.containerViewBottomConstraint?.constant = 0
  228.             // Call this to trigger refresh constraint
  229.             self.view.layoutIfNeeded()
  230.         }
  231.     }
  232.    
  233.     func animateDismissView() {
  234.         // hide main container view by updating bottom constraint in animation block
  235.         UIView.animate(withDuration: 0.3) {
  236.             self.containerViewBottomConstraint?.constant = self.defaultHeight
  237.             // call this to trigger refresh constraint
  238.             self.view.layoutIfNeeded()
  239.         }
  240.        
  241.         // hide blur view
  242.         dimmedView.alpha = maxDimmedAlpha
  243.         UIView.animate(withDuration: 0.4) {
  244.             self.dimmedView.alpha = 0
  245.         } completion: { _ in
  246.             // once done, dismiss without animation
  247.             self.dismiss(animated: false)
  248.         }
  249.     }
  250.    
  251.     func setupConstraints() {
  252.         // 4. Add subviews
  253.         view.addSubview(dimmedView)
  254.         view.addSubview(containerView)
  255.         dimmedView.translatesAutoresizingMaskIntoConstraints = false
  256.         containerView.translatesAutoresizingMaskIntoConstraints = false
  257.        
  258.         containerView.addSubview(titleLabel)
  259.         containerView.addSubview(lineView)
  260.         containerView.addSubview(contentStackView)
  261.         contentStackView.translatesAutoresizingMaskIntoConstraints = false
  262.        
  263.         // 5. Set static constraints
  264.         NSLayoutConstraint.activate([
  265.             // set dimmedView edges to superview
  266.             dimmedView.topAnchor.constraint(equalTo: view.topAnchor),
  267.             dimmedView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
  268.             dimmedView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  269.             dimmedView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  270.             // set container static constraint (trailing & leading)
  271.             containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  272.             containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  273.             //containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: defaultHeight)
  274.         ])
  275.        
  276.         // 6. Set container to default height
  277.         containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: defaultHeight)
  278.         // 7. Set bottom constant to 0
  279.         containerViewBottomConstraint = containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
  280.         // Activate constraints
  281.         containerViewHeightConstraint?.isActive = true
  282.         containerViewBottomConstraint?.isActive = true
  283.        
  284.         NSLayoutConstraint.activate([
  285.             // TitleLbl
  286.             titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 12),
  287.             titleLabel.heightAnchor.constraint(equalToConstant: 20),
  288.             titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10),
  289.             titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -10),
  290.         ])
  291.        
  292.         NSLayoutConstraint.activate([
  293.             // Line View
  294.             lineView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5),
  295.             lineView.heightAnchor.constraint(equalToConstant: 1),
  296.             lineView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -10),
  297.             lineView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10)
  298.         ])
  299.        
  300.         NSLayoutConstraint.activate([
  301.             // ..
  302.             // content stackView
  303.             contentStackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 32),
  304.            // contentStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20),
  305.             contentStackView.heightAnchor.constraint(equalToConstant: 150),
  306.             contentStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20),
  307.             contentStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20),
  308.         ])
  309.     }
  310.    
  311. }
  312.  
  313.  
  314.  
  315.  
  316.  
  317.         let vc = ShareBottomSheetVC()
  318.         vc.modalPresentationStyle = .overCurrentContext
  319.         // Keep animated value as false
  320.         // Custom Modal presentation animation will be handled in VC itself
  321.         self.present(vc, animated: false)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement