0

The question is all about not one thing it has the lots of query's

  1. I need the layer of middle selected dates looks like something connected to the first and last date reference image i've attached

  2. The date which i've printed while selecting is mis matching with the date that i've actually picked

  3. In the case of selecting one date , i need to unselect it instantly by clicking it again means that does't works it took one more click on that time also didselect only triggers

  4. I need the spacing between the rows of dates also , i need the calendar to show the particular date from dd-mm-yy to dd-mm-yy with not like the pagination it should be in the entire view

  5. I need to edit the UI appearance of the FSCalendar for my needs.

==> I have attached my code for the FSCalendarViewController , FSCalendarCell , with my output and desired output , could anyone help me out of this task.

import Foundation
import FSCalendar

class CalanderViewController: UIViewController {
  
    fileprivate let gregorian = Calendar(identifier: .gregorian)
    
    fileprivate let formatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM-yyyy"
        formatter.locale = Locale(identifier: "en_IN")
        return formatter
    }()
    
    fileprivate weak var calendar: FSCalendar!
        
    // first date in the range
    private var firstDate: Date?
    
    // last date in the range
    private var lastDate: Date?
    
    private var datesRange: [Date]? = []
        
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK:- Life cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationBar()
        setupViews()
        setupConstraints()
    }
    
    private func setupNavigationBar() {
        self.navigationItem.title = "Calendar"
        navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black, .font: FontFamily.Poppins.bold.font(size: 15)!]
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "",style: .plain,target: nil,action: nil)
    }
    
    private func setupViews() {
        view.backgroundColor = .white
        
        let calendar = FSCalendar()
        calendar.dataSource = self
        calendar.delegate = self
        
        calendar.translatesAutoresizingMaskIntoConstraints = false
        calendar.allowsSelection = true
        calendar.allowsMultipleSelection = true
        calendar.swipeToChooseGesture.isEnabled = false
        calendar.pagingEnabled = true
        
        calendar.today = nil

        calendar.scrollDirection = .vertical
        calendar.appearance.eventOffset = CGPoint(x: 0, y: -7)
        calendar.rowHeight = 60

        calendar.register(DIYCalendarCell.self, forCellReuseIdentifier: "cell")
                
        let scopeGesture = UIPanGestureRecognizer(target: calendar, action: #selector(calendar.handleScopeGesture(_:)));
        calendar.addGestureRecognizer(scopeGesture)
        
        view.addSubview(calendar)
        self.calendar = calendar
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            self.calendar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor , constant: 25),
            self.calendar.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 20),
            self.calendar.rightAnchor.constraint(equalTo: view.rightAnchor , constant: -25),
            self.calendar.heightAnchor.constraint(equalToConstant: 300)
        ])
    }
    
    
    // MARK: - Private functions
    
    func datesRange(from: Date, to: Date) -> [Date] {
        // in case of the "from" date is more than "to" date,
        // it should returns an empty array:
        
        if from > to {
            return [Date]()
        }
        
        var tempDate = from
        var array = [tempDate]
        
        while tempDate < to {
            tempDate = Calendar.current.date(byAdding: .day, value: 1, to: tempDate)!
            array.append(tempDate)
        }
        
        return array
    }
    
    private func configureVisibleCells() {
        calendar.visibleCells().forEach { (cell) in
            let date = calendar.date(for: cell)
            let position = calendar.monthPosition(for: cell)
            self.configure(cell: cell, for: date!, at: position)
        }
    }
    
    private func configure(cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {
        
        let diyCell = (cell as! DIYCalendarCell)
        // Configure selection layer
        if position == .current {
            
            var selectionType = SelectionType.none
            
            if calendar.selectedDates.contains(date) {
                let previousDate = self.gregorian.date(byAdding: .day, value: -1, to: date)!
                let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: date)!
                if calendar.selectedDates.contains(date) {
                    if calendar.selectedDates.contains(previousDate) && calendar.selectedDates.contains(nextDate) {
                        selectionType = .middle
                    }
                    else if calendar.selectedDates.contains(previousDate) && calendar.selectedDates.contains(date) {
                        selectionType = .rightBorder
                    }
                    else if calendar.selectedDates.contains(nextDate) {
                        selectionType = .leftBorder
                    }
                    else {
                        selectionType = .single
                    }
                }
            }
            else {
                selectionType = .none
            }
            if selectionType == .none {
                diyCell.selectionLayer.isHidden = true
                return
            }
            diyCell.selectionLayer.isHidden = false
            diyCell.selectionType = selectionType
            
        } else {
            diyCell.selectionLayer.isHidden = true
        }
    }
}


extension CalanderViewController:  FSCalendarDataSource, FSCalendarDelegate, FSCalendarDelegateAppearance {
    func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
        let cell = calendar.dequeueReusableCell(withIdentifier: "cell", for: date, at: position)
        return cell
    }
    
    func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {
        self.configure(cell: cell, for: date, at: position)
    }
    
    func calendar(_ calendar: FSCalendar, titleFor date: Date) -> String? {
        return nil
    }
    
    // MARK:- FSCalendarDelegate
    
    func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
        self.calendar.frame.size.height = bounds.height
    }
    
    func calendar(_ calendar: FSCalendar, shouldSelect date: Date, at monthPosition: FSCalendarMonthPosition)   -> Bool {
        return monthPosition == .current
    }
    
    func calendar(_ calendar: FSCalendar, shouldDeselect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool {
        return monthPosition == .current
    }
    
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        if firstDate == nil {
            firstDate = date
            datesRange = [firstDate!]
            
            print("datesRange contains: \(datesRange!)")
            self.configureVisibleCells()
            return
        }
        
        // only first date is selected:
        if firstDate != nil && lastDate == nil {
            // handle the case of if the last date is less than the first date:
            if date <= firstDate! {
                calendar.deselect(firstDate!)
                firstDate = date
                datesRange = [firstDate!]
                
                print("datesRange contains: \(datesRange!)")    
                self.configureVisibleCells()
                return
            }
            
            
            let range = datesRange(from: firstDate!, to: date)
            
            lastDate = range.last
            
            for d in range {
                calendar.select(d)
            }
            
            datesRange = range
            self.configureVisibleCells()
            
            return
        }
        
        // both are selected:
        if firstDate != nil && lastDate != nil {
            for d in calendar.selectedDates {
                calendar.deselect(d)
            }
            
            lastDate = nil
            firstDate = nil
            
            datesRange = []
            
            print("datesRange contains: \(datesRange!)")
        }
        
        self.configureVisibleCells()
    }
    
    func calendar(_ calendar: FSCalendar, didDeselect date: Date) {
        // NOTE: the is a REDUANDENT CODE:
        if firstDate != nil && lastDate != nil {
            
            for d in calendar.selectedDates {
                calendar.deselect(d)
            }
            
            lastDate = nil
            firstDate = nil
            
            datesRange = []
            print("datesRange contains: \(datesRange!)")
        }
        
        if (firstDate != nil && lastDate == nil) {
            lastDate = firstDate
            
            var range = datesRange(from: firstDate!, to: firstDate!)
            if (range.count >= 1) {
                range.append(range[0])
            }
            
            lastDate = range.last
            
            for d in range {
                calendar.select(d)
            }
            
            datesRange = range
            print("datesRange contains: \(datesRange!)")
            
        }
        
        self.configureVisibleCells()
    }
    
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
        if self.gregorian.isDateInToday(date) {
            return [UIColor.yellow]
        }
        return [appearance.eventDefaultColor]
    }
    
    func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
        return 0
    }

}

:- Here is my cell

import Foundation
import UIKit
import FSCalendar

enum SelectionType : Int {
    case none
    case single
    case leftBorder
    case middle
    case rightBorder
}

class DIYCalendarCell: FSCalendarCell {
    
    weak var selectionLayer: CAShapeLayer!
    
    var selectionType: SelectionType = .none {
        didSet {
            setNeedsLayout()
        }
    }
    
    required init!(coder aDecoder: NSCoder!) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupObjects()
    }
    
    private func setupObjects() {
        
        let selectionLayer = CAShapeLayer()
        selectionLayer.fillColor = Asset.Colors.backgroundGray.color.cgColor
        selectionLayer.actions = ["hidden": NSNull()]
        
        self.contentView.layer.insertSublayer(selectionLayer, below: self.titleLabel!.layer)
        self.selectionLayer = selectionLayer
        
        self.shapeLayer.isHidden = true
        self.eventIndicator.isHidden = true
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        self.backgroundView?.frame = self.bounds.insetBy(dx: 1, dy: 1)
        self.selectionLayer.frame = self.contentView.bounds
        
        self.eventIndicator.isHidden = true
        
        let diameter: CGFloat = min(self.selectionLayer.frame.height, self.selectionLayer.frame.width)
        let middleRect = CGRect(x: self.contentView.frame.width / 2 - diameter / 2,
                                y: self.contentView.frame.height / 2 - diameter / 2,
                                width: diameter, height: diameter)
        
        if selectionType == .middle {
            let path = UIBezierPath(rect: self.selectionLayer.bounds)
            
            self.selectionLayer.path = path.cgPath
            self.selectionLayer.fillColor = Asset.Colors.backgroundGray.color.cgColor
            self.titleLabel.textColor = .black
            
            // Add rounded corners to the start and end of the row
            
            let cornerRadius: CGFloat = self.selectionLayer.frame.height/2
            
            if self.frame.minX == self.superview?.subviews.first?.frame.minX {
                let maskLayer = CAShapeLayer()
                maskLayer.frame = self.bounds
                maskLayer.path = UIBezierPath(roundedRect: maskLayer.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
                self.layer.mask = maskLayer
            } else if self.frame.maxX == self.superview?.subviews.last?.frame.maxX {
                let maskLayer = CAShapeLayer()
                maskLayer.frame = self.bounds
                maskLayer.path = UIBezierPath(roundedRect: maskLayer.bounds, byRoundingCorners: [.bottomRight, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
                self.layer.mask = maskLayer
            } else {
                self.layer.mask = nil
            }
        }
        
        else if (selectionType == .rightBorder || selectionType == .leftBorder) {
            let path = UIBezierPath(roundedRect: self.selectionLayer.bounds, cornerRadius: self.selectionLayer.bounds.size.height/2)
            self.selectionLayer.path = path.cgPath
            self.selectionLayer.fillColor = UIColor.black.withAlphaComponent(0.8).cgColor
            self.titleLabel.textColor = .white
            
            // Remove rounded corners from the start and end of the row
            self.layer.mask = nil
        }
        
        else if selectionType == .single {
            self.selectionLayer.path = UIBezierPath(ovalIn: middleRect).cgPath
            self.selectionLayer.fillColor = UIColor.black.withAlphaComponent(0.8).cgColor
            self.titleLabel.textColor = .white
        }
    }
    
    override func configureAppearance() {
        super.configureAppearance()
        if self.isPlaceholder {
            self.eventIndicator.isHidden = true
            self.titleLabel.textColor = UIColor.lightGray
            self.titleLabel.backgroundColor = .clear
        }
    }
    
}

My actual output My actual output

My desired output My desired output

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276

0 Answers0