Pass UITableView default PanGesture to another UIView - swift

I have an UITableView, vertically covered over a UIView and that View have UIPangestureRecognizer. Now if user try to scroll tableView and if finger point have no cell I want second views panGesture response.

Subclass UITableView and override hitTest method.
-> if finger point have cell return self
-> else return nil or any other view that should response
Code:
class MyTableView: UITableView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if indexPath(at: point) != nil {
retrn self
} else {
return nil
}
}
}

Related

Scroll all collection view cell inside table view

I have collection view inside a table view.
It looks like a spreadsheet:
Collection view scrolls horizontally and table view vertically.
I want all the collection view to scroll at once.
Can anyone help me with the solution?
protocol CollectionViewScrollDelegate{
func didScrollCollectionView(contentOffset: CGPoint)
}
extension CellTableView : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if delegate != nil{
delegate?.didScrollCollectionView(contentOffset: collectionView.contentOffset)
}
}
}
var requiredContentOffset:CGPoint!
extension MyVC : CollectionViewScrollDelegate{
func didScrollCollectionView(contentOffset: CGPoint) {
requiredContentOffset = contentOffset
let cells = tblView.visibleCells as! Array<CellTableView>
for cell in cells {
cell.collectionView.contentOffset = contentOffset
}
}
}
extension MyVC : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let cells = tblView.visibleCells as! Array<CellTableView>
for cell in cells {
cell.collectionView.contentOffset = requiredContentOffset
}
}
}

why is my custom UIButton subclass not triggering touch up IBAction?

this probably has something to do with the fact that I am overriding touchesBegan in my custom subclass:
class myCustomButton: UIButton {
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
print("TOUCHES BEGAN OVERRIDE CALLED")
originalColor = self.backgroundColor ?? .red
self.backgroundColor = .white
self.animateScale()
setNeedsDisplay()
}
//...
}
touchesEnded is similarly overridden, and both of these functions are called and print debug statements to console on button press. however, the code in the button pressed IBAction from my view controller is never run
#IBAction func nextButtonPressed(_ sender: Any) {
print("this is never executed")
//...
}
I have double checked that the IBOutlet is not broken and changed the (_ sender: Any) to (_ sender: AnyObject) both to no avail. any insights would be appreciated
ah ok, figured it out. in my custom button initializer I added a subview which, by default, had userInteractionEnabled set to true. setting userInteractionEnabled = true on my custom button object and userInteractionEnabled = false on my view before adding it as a subview allows my custom button to capture the touch and handle it properly rather than the view capturing the touch
Instead of overriding touchesBegan, try overriding the sendAction function of your UIButton:
override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event)
//Insert your code here
}

Selecting UICollectionViewCell in the presence of UITapGestureRecognizer

I am trying to respond to UICollectionViewCell selection:
private func setupCellAction() {
collectionView?.rx.itemSelected
.asObservable()
.subscribe(onNext: { [weak self] indexPath in
print("itemSelected!")
let cell = self?.collectionView?.cellForItem(at: indexPath) as? CellTypeCollectionViewCell
self?.performSegue(withIdentifier: "showBarchartSegue", sender: cell)
}).disposed(by: disposeBag)
}
But somehow onNext method is never called. I tried putting setupCellAction() in viewDidLoad, viewWillAppear and viewDidAppear but it is not working. Any suggestions would be greatly appreciated.
Update
I tried the suggestion from the following thread: How to select CollectionView cell in RxSwift
and added .debug("RX: Model selected") before the subscribe method. I see the output in the console that it is subscribed once.
Update
I tried rewriting the setupCellAction() in the following way:
private func setupCellAction() {
collectionView?.rx.modelSelected(CellTypeCollectionViewCell.self)
.asObservable()
.debug("RX: Model selected")
.subscribe(onNext: { [weak self] cell in
print("itemSelected!")
self?.performSegue(withIdentifier: "showBarchartSegue", sender: cell)
}).disposed(by: disposeBag)
}
It is not working either. I see also that it is subscribed once in the console.
Update
UICollectionViewController was embedded in another container UIViewController, and in it I defined UITapGestureRecognizer. After commenting out the code for the UITapGestureRecognizer, the itemSelected() method started to work. Right now I need a way to let the tap event through if it happened on the UICollectionViewCell. Is there a way to do that?
The code for tapping in the container controller (viewDidLoad):
let tap = UITapGestureRecognizer(target: self, action:
#selector(self.handleTap(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
The handleTap():
#objc func handleTap(_ sender: UITapGestureRecognizer) {
tableView.isHidden = true
searchBar.resignFirstResponder()
}
You can let taps through with the UIGestureRecognizerDelegate protocol and implementing the method
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool.
Basically, you need to return false whenever you touch a UICollectionViewCell, if I understood the problem correctly.
You can do this by using the method func indexPathForItem(at point: CGPoint) -> IndexPath? from the UICollectionView. If the given CGPoint matches a cell's location, you will get its IndexPath.
Don't forget to translate the touch location to the collection view's frame - you can use UITouch's func location(in view: UIView?) -> CGPoint for this.
It would probably look somewhat like this:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: collectionView)
return collectionView.indexPathForItem(at: point) == nil
}

Flexible editable UITextView in UITableView: won’t show the last line under the keyboard

I have a flexible editable UITextView in a UITableViewCell. The complete source code of a simple project can be found at https://github.com/AlexChekanov/TextViewInTableView
Everything works fine. I recalculate the cell height each time the TextView height changes and I scroll the table to the cursor.
class TextViewTableViewCell: UITableViewCell, UITextViewDelegate {
weak var tableView: UITableView?
var textViewHeight: CGFloat?
#IBOutlet weak var textView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
textViewHeight = textView.intrinsicContentSize.height
textView.becomeFirstResponder()
}
func textViewDidChangeSelection(_ textView: UITextView) {
guard let tableView = tableView else { return }
selfUpdate(in: tableView)
scrollToCursor()
}
func selfUpdate(in tableView: UITableView) {
// Do nothing if the height wasn't change
guard textViewHeight != textView.intrinsicContentSize.height else { return }
textViewHeight = textView.intrinsicContentSize.height
// Disabling animations gives us our desired behaviour
UIView.setAnimationsEnabled(false)
/* These will causes table cell heights to be recaluclated,
without reloading the entire cell */
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
func scrollToCursor() {
guard let tableView = tableView else { return }
if let currentCursorPosition = textView.selectedTextRange?.end {
print(currentCursorPosition)
let caret = textView.caretRect(for: currentCursorPosition)
print(caret)
tableView.scrollRectToVisible(caret, animated: false)
}
}
}
The only problem is that when I add the last line or several empty lines at the bottom, table view doesn't scroll. But if I add any symbol to this empty line, it scrolls.
Thank you for help.
UITextView's allow scrolling by default which could possibly be the issue. When the size of the text within the UITextView is larger than the UITextView itself, it allows you to scroll. This may be conflicting with the UITableView's scrolling. Simply use this to be safe:
self.textView.isScrollEnabled = false
self.textView.isEditable = false
I found a solution:
class TextViewTableViewCell: UITableViewCell, UITextViewDelegate {
weak var tableView: UITableView?
var textViewHeight: CGFloat?
#IBOutlet weak var textView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
textViewHeight = textView.intrinsicContentSize.height
textView.becomeFirstResponder()
}
func textViewDidChangeSelection(_ textView: UITextView) {
guard let tableView = tableView else { return }
selfUpdate(in: tableView)
// Here is the trick!
if textView.textStorage.string.hasSuffix("\n") {
CATransaction.setCompletionBlock({ () -> Void in
self.scrollToCaret(textView, animated: false)
})
} else {
self.scrollToCaret(textView, animated: true)
}
}
func scrollToCaret(_ textView: UITextView, animated: Bool) {
guard let tableView = tableView else { return }
guard let currentCursorPosition = textView.selectedTextRange?.end else { return }
var caret = textView.caretRect(for: currentCursorPosition)
caret.size.height += textView.textContainerInset.bottom * 2
tableView.scrollRectToVisible(caret, animated: animated)
}
func selfUpdate(in tableView: UITableView) {
// Do nothing if the height wasn't change
guard textViewHeight != textView.intrinsicContentSize.height else { return }
textViewHeight = textView.intrinsicContentSize.height
// Disabling ansimations gives us our desired behaviour
UIView.setAnimationsEnabled(false)
/* These will causes table cell heights to be recaluclated,
without reloading the entire cell */
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
}

swift unhide buttons in one column if mouse is over row

I working with swift 4 for osx.
I have a view based NSTableView with 4 columns.
the cells in each column has got the same custom cell class:
class CustomCell: NSTableCellView {
#IBOutlet weak var btnInfo: NSButton!
private var trackingArea: NSTrackingArea!
override func awakeFromNib() {
super.awakeFromNib()
self.trackingArea = NSTrackingArea(
rect: bounds,
options: [.activeAlways, .mouseEnteredAndExited],
owner: self,
userInfo: nil
)
addTrackingArea(trackingArea)
}
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
btnInfo.isHidden = false
}
override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
btnInfo.isHidden = true
}
}
Now i would like to realize the following situation:
if the user goes with the mouse over a row, the btnInfo should be visible and hide again, it the mouse leaves the row.
problem is (with the code above), that my apps crashes, because btnInfo will be nil
Logically: Because this button is only in column 4 available.
in all other columns it will be nil.
how can i solve this?
The solution is to add an NSTrackingArea to the entire view, not the individual cells. Then on the entire table view, you can get the mouse move events, take the NSEvent's locationInWindow. Then NSTableView has a method row(at point: NSPoint) -> Int that can get you the current row that should be highlighting the button.