iOS - How can I click a UIButton behind UITableView with transparent header - ios

I have a UIButton at the bottom layer of the UIViewController.
And I have a UITableView (full screen size) on top of the UIButton, the UITableView has a header (UIView) which has a transparent background which could be able to see through and show the UIButton.
The UIButton is not clickable even when the button appear behind the tableview header.
My tableView's cell and the header of the tableView has buttons on it, so I could not set headerView.userInteraction = true or tableView.userInteraction = true
I have tried to use pointInside:withEvent: and hitTest:withEvent:, UIButton is still not clickable in both cases.
Any suggestions? Thanks

You could use UITapGestureRecognizer for example like this:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
...
let headerViewGesture = UITapGestureRecognizer(target:self, action:#selector(MyClass.headerViewTap(_:)))
headerView.addGestureRecognizer(mainViewGesture)
...
}
func headerViewTap(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.Recognized
{
let touchLocation = recognizer.locationInView(recognizer.view)
print(touchLocation)
if CGRectContainsPoint(myButton.frame, touchLocation) {
print("button was touched")
}
}
}

Please try bringSubviewToFront to get the button clickable.
self.view.bringSubviewToFront(yourButtton)
Or you can check it in storyboard whether the button is inside some view, And add make sure the button is placed properly in storyboard. The button should be below the table view like in the sample picture.
You can try both the ways and let me know if you have any doubt.

Related

Touches should cancel function for UITableView is not called

I have a table view that has a UIImage and some UIButton objects in each TableView cell. When I scroll the table view, it works quite well overall. However, if I touch one of the UIButton items to scroll the table view, the UIButton seems to steal the touches and the table view does not scroll. Instead the UIButton items appears to be selected instead. I would like to be able to scroll the table view even when the user touches buttons when starting to scroll. So, I searched for solutions here, tried the following.
extension UITableView {
override public func touchesShouldCancel(in view: UIView) -> Bool {
print("the touchesShouldCancel function is called.")
if view is UIButton {
return true
}
return super.touchesShouldCancel(in: view)
}
}
However, it doesn't work. The function does not even get called whenever I scroll the table view. What am I missing here? I would greatly appreciate your input. Thanks all.
Subclass UITableView Set tableView canCancelContentTouches to true as per Apple docs
The scroll view does not call this method if the value of the
canCancelContentTouches property is false
class YourTableView:UITableView {
override func awakeFromNib() {
canCancelContentTouches = true
delaysContentTouches = false
}
override func touchesShouldCancel(in view: UIView) -> Bool {
}
}
You need to make a UITableView subclass
class SubTbl:UITableView {
// add your method
}
Then assign it to that table in IB or use it in code

iOS 11 scroll to top when using large titles doesn't work properly

When using large titles and tapping the status bar to scroll to the top of a UIScrollView or UITableView (probably also UICollectionView, haven't tested this) it always goes a little too far.
I have refresh enabled on my TableView and when tapping the status bar it appears like this and stays that way until I tap the screen.
I have a ScrollView in another ViewController and if I tap the status bar there it also scrolls a little bit too far, making the navigation bar too tall. This also returns to normal when I tap somewhere or scroll a tiny bit.
Normal:
After I tapped the status bar:
This also only happens when I have large titles activated, using normal titles everything works as it should.
Any ideas how to fix this?
How to recreate:
Create a new project with a navigation controller and a UIViewController with a TableView inside.
Set navigation controller to prefer large titles. Turn translucent off. Set title on UIViewController
Set constraints on TableView to pin to the edges of the ViewController
Create outlet for TableView in the ViewController
Implement delegates and set a number of rows, for example 100
Launch app
Scroll down so the large title becomes a normal title
Tap status bar so the tableView scrolls to the top
Now the title is not at the position it should be, if you now scroll a tiny bit up or down it snaps back to the normal position.
ViewController code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath)
return cell
}
}
Okay so I found why the problem occurs, but not how to fix it in that exact scenario.
If you're using large titles and a UITableViewController with the navigation bar translucency set to off the problem will occur.
When you turn translucent back on the problem goes away.
If you're using a TableView in a normal UIViewController the problem always occurs.
Edit
Turns out setting "extendedLayoutIncludesOpaqueBars = true" fixes the problem if you're using a translucent navigation bar!
Similar question: UIRefreshControl() in iOS 11 Glitchy effect
I faced the same issue. I had a collectionview in UIViewController embedded in UINavigationController. Collectionview had leading, trailing, top, bottom constraints to a safe area.
To fix this you need:
Change the top constraint of collectionview to supervises (NOT safe area)
Set extendedLayoutIncludesOpaqueBars = true in viewDidLoad method
After many hours of tests i found a solution that works with UIViewController.
In UIViewController with UITableView, you should:
in viewDidLoad set extendedLayoutIncludesOpaqueBars = true
in storyboard pin tableView top constraint to superview top with constant = 0 (tableView will be under navigationbar and statusbar when navigationbar is translucent)
After that if you tap on statusbar, tableview stops in the right place.
In your ViewController declare a var didBeginScrollToTop of Bool type.
Assign the controller as scrollView's delegate. Then
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
didBeginScrollToTop = true
return true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard didBeginScrollToTop, scrollToTopGestureTargetView?.contentOffset.y ?? 0 < -25 else { return }
scrollToTopGestureTargetView?.setContentOffset(.zero, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
didBeginScrollToTop = false
}
What this does is adjusting your content offset when bouncing. The flag is there to catch the scrollToTop gesture and is reset on scroll. You can also set it off after setting the .zero offset. If you have implementation with child VCs, don't forget to call super when overriding those 3 delegate methods.
Now my first approach was to track when the content offSet is < 0, but on some devices it did not expand the navigation item and that magic -25 seemed to work on all simulators. You can combine this approach with an implementation that returns the size of the nav bar and replace it, but as far as I am aware there wasn't an easy way to get the nav bar frame/bounds so easily.
Note you may have to handle also this: Strange velocity prefers large title
I've fix it with maually setting the contentOffset to 0 after scroll to top.
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return true
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
scrollView.contentOffset.y = 0.0
}

how to remove UIButton from stackview if there is no content

I have four buttons in a stackView in a collectionViewCell
I am performing segue to a newVC if the button is tapped, the content i am displaying in the newVC is coming via JSON. If the url is empty or url.characters.count == 0 for the respective button, i want to remove it from the cell and resize the rest of the buttons equally in the cell. How can i achieve that.
With UIStackView it is very simple. Just mark the button as hidden and the UIStackView will resize itself.
func buttonTapped(_ button: UIButton) {
button.isHidden = shouldHideButton()
performSegue(withIdentifier: "YourSegueIdentifier", sender: self)
}
func shouldHideButton() {
...
}
Also as venkat mentioned you can use removeArrangedSubview(_:) instead of just hiding, which will remove the button permanently.
I recommand you to read UIStackView documentation.
Since you're working in a StackView, the other items inside it will automatically adjust when we hide your UIButton.
In order to hide you UIButton based on your URL's character count, we can do the following:
button.isHidden = url.characters.count == 0
To simplify even further, we can just check for .isEmpty like so:
button.isHidden = url.isEmpty

TableViewCell disclosure indicator position

I have a custom tableviewcell with the standard disclosure indicator positioned in the centre vertically by default. How can I move this standard disclosure indicator vertically like in the iOS mail app?
You can adjust the position of the default accessory by subclassing your cell and finding the UIButton in the cell's subviews (which represents the indicator) and holding a reference to it. You can then simply update the frame of the accessoryButton in layoutSubviews or wherever you choose like so:
class CellSubclass: UITableViewCell {
var accessoryButton: UIButton?
override func awakeFromNib() {
super.awakeFromNib()
accessoryType = .DisclosureIndicator
accessoryButton = subviews.flatMap { $0 as? UIButton }.first
}
override func layoutSubviews() {
super.layoutSubviews()
accessoryButton?.frame.origin.y = 8
}
}
You can not move standard disclosure indicator vertically.
If you want to achieve this functionality, then you need to use your custom cell and use image of disclosure indicator and then you can set this image with button on where ever you want to place.
Or you can use view also as a accessory indicator and add as a subview of UITableViewCell
[cell.contentView addSubview:aView];
You can read more over here disclosure indicator
You can modify the Layout Margins to Fixed on TableViewCell in the .xib and then set the right margin to the desired value.

Switching to UIPanGestureRecognizer when TableView reaches it top

I have a UITableView added to UIViewController. Behind that TableView there is UIImageView like this:
The default behavior of a UITableView is to show whitespace when scrolling up and there is no more content to show. I would like to override this and switch to custom UIPanGestureRecognizer that will move the whole tableview down.
To clarify: When the tableview reaches it top I would like to move the entire tableview instead.
I have set up my PanGestureRecognizer and added it to the tableview and tried this:
func scrollViewDidScroll(scrollView: UIScrollView) {
if self.tableView.contentOffset.y == 0{ //tableview reaches its top
panGestureRecognizer.enabled = true
}else{
panGestureRecognizer.enabled = false
}
}
This does not work at all since the the scrollViewDidScroll triggers at the end of the gesture.
Any other ideas on how I can implement this?

Resources