如何从嵌入在 UIScrollView 中的 UiView 中的 CollectionView 接收触摸事件
How to receive touch events from a CollectionView embedded inside a UiView embedded inside a UIScrollView
我有一个很长的表单并创建了一个包含自定义日历和几个文本字段的容器视图 (UIView)。 UIView 嵌入在 scrollView 中,这样我就可以滚动长表单。
ScrollView
-ContainerView // UIView
-CustomCalendar // contains a UICollectionView
-TextField
-TextField
//etc etc..
苹果说:
You should not embed UIWebView or UITableView objects in UIScrollView
objects. If you do so, unexpected behavior can result because touch
events for the two objects can be mixed up and wrongly handled.
为了解决这个问题,我尝试禁用 CalendarView 上的滚动,但没有任何区别:
// this is inside the CalenderView file
myCollectionView.isScrollEnabled = false
我从 here and here 获得了日历,它使用 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 的文件。还有 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) 是一个 collectionView。
我从@zambrey 找到了 answer here by @Jaydeep and got the explanation。
想法是告诉手势识别器不要吞掉触摸事件。为此,您需要将 singleTap 的 cancelsTouchesInView 属性 设置为 NO,默认情况下为 YES。
你想告诉 scrollView 不要吃掉所有的触摸事件,你可以通过向它添加一个点击手势识别器并设置点击的
singleTap.cancelsTouchesInView = false
我在将 CalenderView 嵌入 ScrollView 内的 UIView 的文件中添加了 tapGesture,而不是在具有 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
}
我有一个很长的表单并创建了一个包含自定义日历和几个文本字段的容器视图 (UIView)。 UIView 嵌入在 scrollView 中,这样我就可以滚动长表单。
ScrollView
-ContainerView // UIView
-CustomCalendar // contains a UICollectionView
-TextField
-TextField
//etc etc..
苹果说:
You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result because touch events for the two objects can be mixed up and wrongly handled.
为了解决这个问题,我尝试禁用 CalendarView 上的滚动,但没有任何区别:
// this is inside the CalenderView file
myCollectionView.isScrollEnabled = false
我从 here and here 获得了日历,它使用 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 的文件。还有 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) 是一个 collectionView。
我从@zambrey 找到了 answer here by @Jaydeep and got the explanation。
想法是告诉手势识别器不要吞掉触摸事件。为此,您需要将 singleTap 的 cancelsTouchesInView 属性 设置为 NO,默认情况下为 YES。
你想告诉 scrollView 不要吃掉所有的触摸事件,你可以通过向它添加一个点击手势识别器并设置点击的
singleTap.cancelsTouchesInView = false
我在将 CalenderView 嵌入 ScrollView 内的 UIView 的文件中添加了 tapGesture,而不是在具有 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
}