Reputation: 19612
I have a very long form and created a containerView (UIView) that contains a custom Calendar and several textFields. The UIView is embedded inside a scrollView so that I can scroll the long form.
ScrollView
-ContainerView // UIView
-CustomCalendar // contains a UICollectionView
-TextField
-TextField
//etc etc..
Apple says:
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.
To get around that I tried to disable scrolling on the CalendarView but it didn't make any difference:
// this is inside the CalenderView file
myCollectionView.isScrollEnabled = false
I got the calendar from here and here and it uses a collectionView to show the dates and when a date is chosen didSelectItem is triggered.
The problem is since the custom calendar contains a collectionView and it's inside the scrollView when I touch a date didSelecetItem won't run.
How can I get the calendar's collectionView to receive touch events?
The calendar works fine when it's not inside the 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...
}
Here is the file for the CalenderView. There are 2 more files that I didn't include because those are only views that create the weeks and months.
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)
}
}
Just for a visual of the CalendarView. The dates (1-30) are a collectionView.
Upvotes: 2
Views: 1287
Reputation: 19612
I found the answer here by @Jaydeep and got the explanation from @zambrey.
The idea is to tell the gesture recognizer to not swallow up the touch events. To do this you need to set singleTap's cancelsTouchesInView property to NO, which is YES by default.
You want to tell the scrollView not to eat all the touch events and you do that by adding a single tap gesture recognizer to it and setting the tap’s
singleTap.cancelsTouchesInView = false
I added the tapGesture in the file that embeds the CalenderView inside the UIView inside the ScrollView and not the file that has the 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
}
Upvotes: 3