I have successfully implemented a search bar, now i want when swipe down the tableview to show search bar, to swipe again down, to hide search bar. What methods should i use?Thank you
A UITableView is a subclass of UIScrollView which has delegate methods (from UIScrollViewDelegate) that you can use to find out when a scroll has started and ended.
You can use the scrollViewDidScroll(_:) method to be notified when the user started scrolling, and the scrollViewDidEndDecelerating(_:) to be notified when the scroll has ended.
From your question, I assume that you already have a method to show/hide the search bar; you are just looking for "when" to call your showSearchBar or hideSearchBar method.
You could have a Bool property that stores whether the searchBar is hidden of not, and call you methods accordingly.
let searchBarIsHidden = true
override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if searchBarIsHidden {
showSearchBar() //your show search bar function
} else {
hideSearchBar() //your hide search bar function
}
}
Now you should make sure you update the value of searchBarIsHidden at the end of your showSearchBar and hideSearchBar
Beautiful hide and show using top constraint of search bar in Swift:
var lastContentOffset:CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let bottomOffset = scrollView.contentSize.height - scrollView.bounds.height
guard scrollView.contentOffset.y < bottomOffset else {
return
}
guard scrollView.contentOffset.y > 0 else {
searchBarTopConstraint.constant = 0
return
}
let offsetDiff = scrollView.contentOffset.y - lastContentOffset
let unsafeNewConstant = searchBarTopConstraint.constant + (offsetDiff > 0 ? -abs(offsetDiff) : abs(offsetDiff))
let minConstant:CGFloat = -searchBar.frame.height
let maxConstant:CGFloat = 0
searchBarTopConstraint.constant = max(minConstant, min(maxConstant, unsafeNewConstant))
lastContentOffset = scrollView.contentOffset.y
}
Related
I have a UIScrollView that sometimes has enough contentHeight to scroll and sometimes doesn't. I also have a button that's predicated on the user scrolling to the bottom of the scroll view.
Without the user taking an action, how do I detect if the scroll view has the contentHeight to scroll so I can set the default isEnabled of the button appropriately?
Thanks!
You can create an extension on UIScrollView
extension UIScrollView {
var contentUntilBotttom: CGFloat {
return max(contentSize.height - frame.size.height - contentOffset.y, 0)
}
var isAtTheBottom: Bool {
return contentUntilBotttom == 0
}
}
In case you are using RxSwift you can observe changes like:
extension Reactive where Base == UIScrollView {
var isAtTheBottom: ControlEvent<Bool> {
let source = contentOffset
.map({ _ -> Bool in
return self.base.isAtTheBottom
})
return ControlEvent(events: source)
}
}
// So then subscribe to it
scrollView.rx.isAtTheBottom
.subscribe(onNext: { (isAtTheBottom) in
// Update anithing you need
})
I have a textField and when I tap it a tableView appear below. When I scroll the tableView down, say 25% of the height of the tableView I want to hide it. Is it possible ? I am using the scrollViewWillBeginDragging function but its not what I want.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview!)
if translation.y > 550 {
self.animateTableView(shouldShow: false)
}
}
Use this UIScrollViewDelegate method:-
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let bottomEdge: CGFloat = scrollView.contentOffset.y + scrollView.frame.size.height
let contentSize = scrollView.contentSize.height * 0.25
if bottomEdge >= contentSize {
/* Code to hide tableView */
}
}
I am trying to make view that is scrollable. That viewcontroller contains also navbar. Now my goal is to resize my view if the title in that view touches the navbar. How should I do it?
This is how my view looks like(note that the navBar is just transparent):
What I want after the title collision:
I know that I can achieve it in scrollViewDidScroll delegate function, but how?
Well you can track label position using convertRect: method as
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let labelTop = label.rectCorrespondingToWindow.minY
let navBottom = self.navigationController?.navigationBar.rectCorrespondingToWindow.maxY
if navBottom == labelTop {
// do what you want to do
}
}
extension UIView{
var rectCorrespondingToWindow:CGRect{
return self.convert(self.bounds, to: nil)
}
}
I want to have my tableViewHeaders visible as the user scrolls by pinning to the top which is the current behaviour in my tableView. However, when the tableView stops scrolling, I want to remove these 'pinned' headers. I am achieving this in my collectionView project using the following in my scrollView delegate methods:
if let cvl = chatCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
cvl.sectionHeadersPinToVisibleBounds = false
cvl.invalidateLayout()
}
Is there a similar way to hide a tableView's 'pinned' (sticky) headers? I am using a tableViewController.
This is my solution to this issue. I wonder if there is a simpler way to do this though.
Please note, this will only work if your header is a UITableViewHeaderFooterView. Not if you are using a UITableViewCell for a header. If you are using a UITableViewCell, tableView.headerView(forSection: indexPathForVisibleRow.section) will return nil.
In order to hide the pinned headers when the tableView stops scrolling and have them re-appear when the tableView starts scrolling again, override these four scrollView delegate methods.
In the first two (scrollViewWillBeginDragging and scrollViewWillBeginDecelerating), get the section header for the first section of the visible rows and make sure it is not hidden.
In the second two delegate methods, do a check to see that for each of the visible rows, the header frame for that row is not overlapping the frame for the row cell. If it is, then this is a pinned header and we hide it after a delay. We need to ensure that the scrollView is not still dragging before removing the pinned header as will be the case when the user lifts their finger but the scroll view continues to scroll. Also because of the time delay, we check that the scrollView is not dragging before removing it in case the user starts scrolling again less than 0.5 seconds after the scroll stops.
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
showPinnedHeaders()
}
override func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
showPinnedHeaders()
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
removePinnedHeaders()
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
removePinnedHeaders()
}
private func showPinnedHeaders() {
for section in 0..<totalNumberOfSectionsInYourTableView {
tableView.headerView(forSection: section)?.isHidden = false
}
}
private func removePinnedHeaders() {
if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows {
if indexPathsForVisibleRows.count > 0 {
for indexPathForVisibleRow in indexPathsForVisibleRows {
if let header = tableView.headerView(forSection: indexPathForVisibleRow.section) {
if let cell = tableView.cellForRow(at: indexPathForVisibleRow) {
if header.frame.intersects(cell.frame) {
let seconds = 0.5
let delay = seconds * Double(NSEC_PER_SEC)
let dispatchTime = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {
if !self.tableView.isDragging && header.frame.intersects(cell.frame) {
header.isHidden = true
}
})
}
}
}
}
}
}
}
Additionally add removePinnedHeaders() to viewDidAppear() and any other rotation or keyboard frame change methods that will scroll your tableView.
In my tvOS app I have a TabBarController with 3 viewControllers. What I want to do is to automatically hide/change focus of the tabBar when I switch to the next viewController.
I saw some posts here, on SO that suggested to change alfa on the tabBar, but I would like to have a slide up animation, same way as it does when you change focus to something in the viewController.
Any kind of help is highly appreciated.
As Charles said.. Something like this in the derived UITabBarController:
var focusOnChildVC : Bool = false {
didSet {
self.setNeedsFocusUpdate()
}
};
override weak var preferredFocusedView: UIView? {
get {
let v : UIView?;
let focused = UIScreen.mainScreen().focusedView
//A bit of a hack but seems to work for picking up whether the VC is active or not
if (focusOnChildVC && focused != nil) {
v = self.selectedViewController?.preferredFocusedView
} else {
//If we are focused on the main VC and then clear out of property as we're done with overriding the focus now
if (focusOnChildVC) {
focusOnChildVC = false
}
v = super.preferredFocusedView;
}
return v
}
}
The basic idea of the solution described below is to subclass UITabBarController and selectively use the super implementation of weak var preferredFocusedView: UIView? { get } or one that returns selectedViewController?.preferredFocusView along with an implementation of didUpdateFocusInContext(_:withAnimationCoordinator:) that sets up an NSTimer that triggers a focus update and sets a flag that controls the preferredFocusView implementation.
More verbosely, Subclass UITabBarController and override didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator). In your implementation (make sure to call the super implementation) you can inspect the context and determine if a descendent view of the tabBar property is the nextFocusedView or the previousFocusedView (and the nextFocusedView is not a descendent).
If the tab bar is gaining focus you can create an NSTimer for the duration that you want to show the tab bar before hiding it. If the tab bar loses focus before the timer fires, invalidate it. If the timer fires, call setNeedsFocusUpdate() followed by updateFocusIfNeeded().
The last piece you need to get this to work is a flag that is set to true while the timer is set. You then need to override weak var preferredFocusedView: UIView? { get } and call the super implementation if the flag is false and if it is true return selectedViewController?.preferredFocusedView.
You can do it in a UITabBarController subclass:
final class TabBarViewController: UITabBarController {
private(set) var isTabBarHidden = false
func setTabBarHidden(_ isHidden: Bool, animated: Bool) {
guard isTabBarHidden != isHidden else {
return
}
var frame: CGRect
let alpha: CGFloat
if isHidden {
frame = tabBar.frame
frame.origin.y -= frame.height
alpha = 0
} else {
frame = tabBar.frame
frame.origin.y += frame.height
alpha = 1
}
let animations = {
self.tabBar.frame = frame
self.tabBar.alpha = alpha
}
if animated {
UIView.animate(withDuration: 0.3, animations: animations)
} else {
animations()
}
isTabBarHidden = isHidden
}
}