Как получить сенсорные события от CollectionView, встроенного в UiView, встроенного в UIScrollView - PullRequest
0 голосов
/ 16 сентября 2018

У меня очень длинная форма и я создал containerView (UIView), который содержит пользовательский календарь и несколько текстовых полей.UIView встроен в scrollView, так что я могу прокрутить длинную форму.

ScrollView
   -ContainerView // UIView
       -CustomCalendar // contains a UICollectionView
       -TextField
       -TextField
        //etc etc..

Apple говорит:

Вы не должны вставлять объекты UIWebView или UITableView в объекты UIScrollView.Если вы это сделаете, это может привести к неожиданному поведению, потому что события касания для двух объектов могут быть смешаны и неправильно обработаны.

Чтобы обойти это, я попытался отключить прокрутку в CalendarView, но это не удалосьне имеет значения:

// this is inside the CalenderView file
myCollectionView.isScrollEnabled = false

Я получил календарь из здесь и здесь , и он использует collectionView для отображения дат, и когда дата выбрана, didSelectItem isсработало.

Проблема в том, что пользовательский календарь содержит коллекцию ViewView, и он находится внутри scrollView, когда я касаюсь даты. didSelecetItem не запускается.

Как получить коллекцию календаря ViewViewполучать сенсорные события?

Календарь работает нормально, когда его нет внутри scrollView

let scrollView: UIScrollView = {
    let sv = UIScrollView()
    sv.translatesAutoresizingMaskIntoConstraints = false
    sv.showsVerticalScrollIndicator = false
    return sv
}()

let containerView: UIView = {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = .white
    return view
}()

let calendarView: CalendarView = {
    let view = CalendarView() // contains a collectionView
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

override func viewDidLoad() {
    super.viewDidLoad()

    createAnchors()
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 1000)
}

func createAnchors() {

    view.addSubview(scrollView)
    scrollView.addSubview(containerView)

    scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true

    containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
    containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    containerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

    calendarView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: verticalPadding).isActive = true
    calendarView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 10).isActive = true
    calendarView.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -10).isActive = true
    calendarView.heightAnchor.constraint(equalToConstant: 290).isActive = true

    // textFields are added...
}

Вот файл для CalenderView.Есть еще 2 файла, которые я не включил, потому что это только представления, которые создают недели и месяцы.

class CalendarView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, MonthViewDelegate {


let myCollectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    let myCollectionView=UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
    myCollectionView.showsHorizontalScrollIndicator = false
    myCollectionView.translatesAutoresizingMaskIntoConstraints=false
    myCollectionView.backgroundColor=UIColor.clear
    myCollectionView.allowsMultipleSelection=false
    myCollectionView.isScrollEnabled = false
    return myCollectionView
}()

var numOfDaysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31]
var currentMonthIndex: Int = 0
var currentYear: Int = 0
var presentMonthIndex = 0
var presentYear = 0
var todaysDate = 0
var firstWeekDayOfMonth = 0   //(Sunday-Saturday 1-7)

override init(frame: CGRect) {
    super.init(frame: frame)

    initializeView()
}

func initializeView() {
    currentMonthIndex = Calendar.current.component(.month, from: Date())
    currentYear = Calendar.current.component(.year, from: Date())
    todaysDate = Calendar.current.component(.day, from: Date())
    firstWeekDayOfMonth=getFirstWeekDay()

    //for leap years, make february month of 29 days
    if currentMonthIndex == 2 && currentYear % 4 == 0 {
        numOfDaysInMonth[currentMonthIndex-1] = 29
    }
    //end

    presentMonthIndex=currentMonthIndex
    presentYear=currentYear

    setupViews()

    myCollectionView.delegate=self
    myCollectionView.dataSource=self
    myCollectionView.register(dateCVCell.self, forCellWithReuseIdentifier: "Cell")
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return numOfDaysInMonth[currentMonthIndex-1] + firstWeekDayOfMonth - 1
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell=collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! dateCVCell
    cell.backgroundColor=UIColor.clear
    if indexPath.item <= firstWeekDayOfMonth - 2 {
        cell.isHidden=true
    } else {
        let calcDate = indexPath.row-firstWeekDayOfMonth+2
        cell.isHidden=false
        cell.lbl.text="\(calcDate)"
        if calcDate < todaysDate && currentYear == presentYear && currentMonthIndex == presentMonthIndex {
            cell.isUserInteractionEnabled=false
            cell.lbl.textColor = UIColor.lightGray
        } else {
            cell.isUserInteractionEnabled=true
            cell.lbl.textColor = Style.activeCellLblColor
        }
    }
    return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let cell=collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor=Colors.darkRed
    let lbl = cell?.subviews[1] as! UILabel
    lbl.textColor=UIColor.white
}

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    let cell=collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor=UIColor.clear
    let lbl = cell?.subviews[1] as! UILabel
    lbl.textColor = Style.activeCellLblColor
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width/7 - 8
    let height: CGFloat = 30
    return CGSize(width: width, height: height)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 8.0
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    return 8.0
}

func getFirstWeekDay() -> Int {
    let day = ("\(currentYear)-\(currentMonthIndex)-01".date?.firstDayOfTheMonth.weekday)!
    //return day == 7 ? 1 : day
    return day
}

func didChangeMonth(monthIndex: Int, year: Int) {
    currentMonthIndex=monthIndex+1
    currentYear = year

    //for leap year, make february month of 29 days
    if monthIndex == 1 {
        if currentYear % 4 == 0 {
            numOfDaysInMonth[monthIndex] = 29
        } else {
            numOfDaysInMonth[monthIndex] = 28
        }
    }
    //end

    firstWeekDayOfMonth=getFirstWeekDay()

    myCollectionView.reloadData()

    monthView.btnLeft.isEnabled = !(currentMonthIndex == presentMonthIndex && currentYear == presentYear)
}

func setupViews() {
    addSubview(monthView)
    monthView.topAnchor.constraint(equalTo: topAnchor).isActive=true
    monthView.leftAnchor.constraint(equalTo: leftAnchor).isActive=true
    monthView.rightAnchor.constraint(equalTo: rightAnchor).isActive=true
    monthView.heightAnchor.constraint(equalToConstant: 35).isActive=true
    monthView.delegate=self

    addSubview(weekdaysView)
    weekdaysView.topAnchor.constraint(equalTo: monthView.bottomAnchor).isActive=true
    weekdaysView.leftAnchor.constraint(equalTo: leftAnchor).isActive=true
    weekdaysView.rightAnchor.constraint(equalTo: rightAnchor).isActive=true
    weekdaysView.heightAnchor.constraint(equalToConstant: 30).isActive=true

    addSubview(myCollectionView)
    myCollectionView.topAnchor.constraint(equalTo: weekdaysView.bottomAnchor, constant: 0).isActive=true
    myCollectionView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive=true
    myCollectionView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive=true
    myCollectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive=true
}

let monthView: MonthView = {
    let v=MonthView()
    v.translatesAutoresizingMaskIntoConstraints=false
    return v
}()

let weekdaysView: WeekdaysView = {
    let v=WeekdaysView()
    v.translatesAutoresizingMaskIntoConstraints=false
    return v
}()

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

//get first day of the month
extension Date {
var weekday: Int {
    return Calendar.current.component(.weekday, from: self)
}
var firstDayOfTheMonth: Date {
    return Calendar.current.date(from: Calendar.current.dateComponents([.year,.month], from: self))!
}
}

//get date from string
extension String {
static var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter
}()

var date: Date? {
    return String.dateFormatter.date(from: self)
}
}

Только для визуального представления CalendarView.Даты (1-30) являются коллекцией.

enter image description here

1 Ответ

0 голосов
/ 16 сентября 2018

Я нашел ответ здесь от @Jaydeep и получил объяснение от @ zambrey.

Идея состоит в том, чтобы сказать распознавателю жестов непроглотить сенсорные события.Чтобы сделать это, вам нужно установить для свойства SingleTap для CancelsTouchesInView значение NO, которое по умолчанию равно YES.

Вы хотите указать scrollView, что он не должен использовать все сенсорные события, и вы делаете это, добавляя один тап.распознаватель жестов к нему и установка крана

singleTap.cancelsTouchesInView = false

Я добавил tapGesture в файл, который встраивает CalenderView в UIView внутри ScrollView, а не в файл, который имеет collectionView.

override func viewDidLoad() {
    super.viewDidLoad()

    createAnchors()

    // add these 4 lines
    let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
    singleTap.cancelsTouchesInView = false
    singleTap.numberOfTapsRequired = 1
    scrollView.addGestureRecognizer(singleTap)
}

// add this target method. I didn't add any code whatsoever inside of it 
func handleTap(_ recognizer: UITapGestureRecognizer) {
  // I literally left this blank
}
...