如何从嵌入在UIScrollView内部的UiView内嵌的CollectionView接收触摸事件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从嵌入在UIScrollView内部的UiView内嵌的CollectionView接收触摸事件相关的知识,希望对你有一定的参考价值。

我有一个很长的表单,并创建了一个包含自定义日历和几个textFields的containerView(UIView)。 UIView嵌入在scrollView中,以便我可以滚动长格式。

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

Apple说:

您不应该在UIS​​crollView对象中嵌入UIWebView或UITableView对象。如果这样做,可能会导致意外行为,因为两个对象的触摸事件可能混淆和错误处理。

为了解决这个问题,我尝试在CalendarView上禁用滚动,但它没有任何区别:

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

我从herehere获得日历,它使用collectionView来显示日期以及何时选择日期,触发didSelectItem。

问题是,因为自定义日历包含一个collectionView,当我触摸日期时,它在scrollView中,didSelecetItem将无法运行。

如何让日历的collectionView接收触摸事件?

当日历不在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的文件。还有两个我没有包含的文件,因为这些文件只是创建周和月的视图。

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)是collectionView。

enter image description here

答案

我在@Jaydeep找到了answer here并从@zambrey获得了explanation

想法是告诉手势识别器不要吞下触摸事件。要执行此操作,您需要将singleTap的cancelsTouchesInView属性设置为NO,默认情况下为YES。

你想告诉scrollView不要吃掉所有的触摸事件,你可以通过添加一个单击手势识别器来设置它并设置水龙头

singleTap.cancelsTouchesInView = false

我在文件中添加了轻触Gesture,它将日历视图嵌入ScrollView内部的UIView内,而不是具有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
}

以上是关于如何从嵌入在UIScrollView内部的UiView内嵌的CollectionView接收触摸事件的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Swift 中嵌入 UITableView 的文本字段中检索数据?

从嵌入在 UIScrollView 中的表中推送视图控制器

当场景嵌入到 NavigationController 中时,如何防止 UIScrollView 被下推?

UIScrollView - 无法查看所有 UIView

如何将 UIScrollView 添加到基于 xib 的视图中

将 UIView 从 xib 添加到 UIScrollView