I want to show / hide UISearchBar programmatically but I am not having the desire results.
What I am trying to do is hide search bar when scrolling down and show it when scrolling up:
I set my UISearchBar like this:
var mySearchcontroller = UISearchController(searchResultsController: nil)
mySearchcontroller.obscuresBackgroundDuringPresentation = false
mySearchcontroller.searchBar.placeholder = "search"
mySearchcontroller.searchBar.delegate = self
definesPresentationContext = true
self.navigationItem.searchController = mySearchcontroller
self.navigationItem.hidesBackButton = true
self.navigationItem.hidesSearchBarWhenScrolling = false
And the result is
I implement the scrollViewDidScroll to make the search bar show or hide when scrolling:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0)
{
navigationItem.hidesSearchBarWhenScrolling = false
}
else
{
navigationItem.hidesSearchBarWhenScrolling = true
}
}
And the result is:
As you realise that the GRP label or a segmented control is been masked by the search bar and I am not having the same effect where the search bar push down all controls (figure 1).
How can I solve this?
You achieve this functionality by following steps:
Give proper Auto-layout to your views.
Set IBOutlet for the Height of your headerview/searchview like:
#IBOutlet weak var constrainHeightHeader: NSLayoutConstraint!
Write the scrollview delegate methods like:
//MARK: - Scrollview delegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
//
self.contentOffSet = self.cwProducts.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//
let scrollPos = self.cwProducts.contentOffset.y
if scrollPos == self.contentOffSet{
return
}
if(scrollPos > self.contentOffSet ){
//Fully hide your toolbar
self.constrainHeightHeader.constant = 0
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded()
}, completion: { (status) in
self.headerVW.isHidden = true
})
} else {
if(self.isFromBanner) {
self.constrainHeightHeader.constant = 0
}else{
self.constrainHeightHeader.constant = 50
}
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded()
}, completion: { (status) in
self.headerVW.isHidden = false
})
}
}
I hope you are getting my point and by minor changes in the above code you will get solution.
It's working in my project 100% tested.
Related
I am have a UITableView in a UIViewController that displays videos. When I scroll down, I would like to animate going from a large title to a small title. I currently use the code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if #available(iOS 11.0, *) {
UIView.animate(withDuration: 0.35, animations: {
if scrollView.contentOffset.y <= 128 {
self.navigationItem.largeTitleDisplayMode = .always
} else {
self.navigationItem.largeTitleDisplayMode = .never
}
})
}
}
But it seems very jumpy when transitioning. What is a better way of doing this?
If you're conforming to UITableViewDelegate add the following code:
var canTransitionToLarge = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
func animate(){
self.navigationController?.navigationBar.setNeedsLayout()
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.25, animations: {
self.navigationController?.navigationBar.layoutIfNeeded()
self.view.layoutIfNeeded()
})
}
if canTransitionToLarge && scrollView.contentOffset.y <= 0 {
self.navigationItem.largeTitleDisplayMode = .always
animate()
canTransitionToLarge = false
}
else if !canTransitionToLarge && scrollView.contentOffset.y > 0 {
self.navigationItem.largeTitleDisplayMode = .never
animate()
canTransitionToLarge = true
}
}
If not conforming to UITableViewDelegate add ScrollViewDelegate to your ViewController:
class ViewController: UIViewController, UIScrollViewDelegate
I'm looking for the best way to invert the place of two UIView, with animation if possible (first i need to change the place, animation is optionnal).
viewController :
So, i want view1 to invert his place with the view2. The views are set with autolayout in the storyboard at the init.
if state == true {
view1 at the top
view2 at the bot
} else {
view 2 at the top
view 1 at the top
}
I've tried to take view.frame.(x, y, width, height) from the other view and set it to the view but it doesn't work.
I think the best way to do this would be to have a topConstraint for both views connected to the header and then change their values and animate the transition. You can do it in a way similar to this:
class MyViewController: UIViewController {
var view1TopConstraint: NSLayoutConstraint!
var view2TopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view1TopConstraint = view1.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: 0)
view1TopConstraint.isActive = true
view2TopConstraint = view2.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: view1.frame.height)
view2TopConstraint.isActive = true
}
func changeView2ToTop() {
UIView.animateWithDuration(0.2, animations: { //this will animate the change between 2 and 1 where 2 is at the top now
self.view2TopConstraint.constant = 0
self.view1TopConstraint.constant = view2.frame.height
//or knowing that view2.frame.height represents 30% of the total view frame you can do like this as well
// self.view1TopConstraint.constant = view.frame.height * 0.3
self.view.layoutIfNeeded()
})
}
You could also create the NSLayoutConstraint in storyboard and have an outlet instead of the variable I have created or set the top constraint for both views in storyboard at "remove at build time" if you are doing both. This way you won't have 2 top constraints and no constraint warrning
I Made an Example: https://dl.dropboxusercontent.com/u/24078452/MovingView.zip
I just created a Storyboard with 3 View, Header, View 1 (Red) and View 2 (Yellow). Then I added IBoutlets and animated them at viewDid Appear, here is the code:
import UIKit
class ViewController: UIViewController {
var position1: CGRect?
var position2: CGRect?
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
position1 = view1.frame
position2 = view2.frame
UIView.animate(withDuration: 2.5) {
self.view1.frame = self.position2!
self.view2.frame = self.position1!
}
}
}
Hello #Makaille I have try to resolve your problem.
I have made an example, which will help you for your required implementation.
Check here: Source Code
I hope, it will going to help you.
#IBAction func toggle(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
if(sender.isSelected){
let topPinConstantValue = layout_view2TopPin.constant
layout_view1TopPin.constant = topPinConstantValue
layout_view2TopPin.priority = 249
layout_view1BottomPin_to_View2Top.priority = 999
layout_view1TopPin.priority = 999
layout_view2BottomPin_ToView1Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
} else {
let topPinConstantValue = layout_view1TopPin.constant
layout_view2TopPin.constant = topPinConstantValue
layout_view1TopPin.priority = 249
layout_view2BottomPin_ToView1Top.priority = 999
layout_view2TopPin.priority = 999
layout_view1BottomPin_to_View2Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
}
sender.isSelected = !sender.isSelected
}
When you scroll the body on iOS 10 Safari the bottom controls hide. Can I prevent that?
I need body to be scrollable.
Here is the code
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translationInView(scrollView).y < 0{
changeTabBar(true, animated: true)
}
else{
changeTabBar(false, animated: true)
}
}
You can also use the other callback method:
func scrollViewDidScroll(scrollView: UIScrollView) {
...
}
but if you choose so, then you most handle multiple calls to the helper method that actually hides the tabBar.
And then you need to add this method that animates the hide/show of the tabBar.
func changeTabBar(hidden:Bool, animated: Bool){
var tabBar = self.tabBarController?.tabBar
if tabBar!.hidden == hidden{ return }
let frame = tabBar?.frame
let offset = (hidden ? (frame?.size.height)! : -(frame?.size.height)!)
let duration:NSTimeInterval = (animated ? 0.5 : 0.0)
tabBar?.hidden = false
if frame != nil
{
UIView.animateWithDuration(duration,
animations: {tabBar!.frame = CGRectOffset(frame!, 0, offset)},
completion: {
println($0)
if $0 {tabBar?.hidden = hidden}
})
}
}
I have a viewController where am showing image for adding the zooming functionality I added the scrollView in viewController and inside of ScrollView I added ImageView everything is working fine expect of one thing, am hiding, and showing the bars (navigation bar + tab bar) on tap but when hiding them my imageView moves upside see the below images
See here's the image and the bars.
Here I just tapped on the view and bars got hidden but as you can see my imageView is also moved from its previous place, this is what I want to solve I don't want to move my imageView.
This is how am hiding my navigation bar:
func tabBarIsVisible() ->Bool {
return self.tabBarController?.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame)
}
func toggle(sender: AnyObject) {
navigationController?.setNavigationBarHidden(navigationController?.navigationBarHidden == false, animated: true)
setTabBarVisible(!tabBarIsVisible(), animated: true)
}
any idea how can I hide and show the bars without affecting my other Views?
The problem is that you need to set the constraint of your imageView to your superView, not TopLayoutGuide or BottomLayoutGuide.
like so:
Then you can do something like this to make the it smootly with animation:
import UIKit
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
var barIsHidden = false
var navigationBarHeight: CGFloat = 0
var tabBarHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.hideAndShowBar))
view.addGestureRecognizer(tapGesture)
navigationBarHeight = (self.navigationController?.navigationBar.frame.size.height)!
tabBarHeight = (self.tabBarController?.tabBar.frame.size.height)!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func hideAndShowBar() {
print("tap!!")
if barIsHidden == false {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: {
// fade animation
self.navigationController?.navigationBar.alpha = 0.0
self.tabBarController?.tabBar.alpha = 0.0
// set height animation
self.navigationController?.navigationBar.frame.size.height = 0.0
self.tabBarController?.tabBar.frame.size.height = 0.0
}, completion: { (_) in
self.barIsHidden = true
})
} else {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: {
// fade animation
self.navigationController?.navigationBar.alpha = 1.0
self.tabBarController?.tabBar.alpha = 1.0
// set height animation
self.navigationController?.navigationBar.frame.size.height = self.navigationBarHeight
self.tabBarController?.tabBar.frame.size.height = self.tabBarHeight
}, completion: { (_) in
self.barIsHidden = false
})
}
}
}
Here is the result:
I have created an example project for you at: https://github.com/khuong291/Swift_Example_Series
You can see it at project 37
Hope this will help you.
I'm implementing a user form in an iOS app that uses both a UIPickerView and UIDatePicker as input devices from a user. I've implemented each of these as a view external to the main UIViewController in the scene and have them showing and hiding using autolayout by adding and removing constraints.
Here's my problem: I'm maintaining separate constraints and hide/show methods for animating each of these views in and out. It's a lot of repeat code and I get the sense that there has to be a cleaner way to do this, since it seems to be a very common design pattern in iOS apps. I'm fairly new at this, so I feel like I'm missing something.
Is there a better design pattern than this for using multiple input devices to UIButtons??
Here's a sampling of the code i'm using to maintain this...
var datePickerViewBottomConstraint: NSLayoutConstraint!
var datePickerViewTopConstraint: NSLayoutConstraint!
var storagePickerViewBottomConstraint: NSLayoutConstraint!
var storagePickerViewTopConstraint: NSLayoutConstraint!
#IBAction func storageButtonClicked(sender: UITextField) {
storagePickerView.translatesAutoresizingMaskIntoConstraints = false
storagePickerViewBottomConstraint = storagePickerView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
UIView.animateWithDuration(0.4, animations: {
self.storagePickerViewTopConstraint.active = false
self.storagePickerViewBottomConstraint.active = true
self.view.layoutIfNeeded()
})
}
#IBAction func datePumpedClicked(sender: UIButton) {
datePickerView.translatesAutoresizingMaskIntoConstraints = false
datePickerViewBottomConstraint = datePickerView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
UIView.animateWithDuration(0.4, animations: {
self.datePickerViewTopConstraint.active = false
self.datePickerViewBottomConstraint.active = true
self.view.layoutIfNeeded()
})
}
#IBAction func datePickerDismiss(sender: AnyObject) {
datePumpedLabel.setTitle(Global.dateFormatter.stringFromDate(datePicker.date), forState: UIControlState.Normal)
datePickerView.translatesAutoresizingMaskIntoConstraints = false
datePickerViewTopConstraint = datePickerView.topAnchor.constraintEqualToAnchor(view.bottomAnchor)
UIView.animateWithDuration(0.4, animations: {
self.datePickerViewBottomConstraint.active = false
self.datePickerViewTopConstraint.active = true
self.view.layoutIfNeeded()
})
}
#IBAction func storagePickerDismiss(sender: AnyObject) {
storagePickerView.translatesAutoresizingMaskIntoConstraints = false
storagePickerViewTopConstraint = storagePickerView.topAnchor.constraintEqualToAnchor(view.bottomAnchor)
UIView.animateWithDuration(0.4, animations: {
self.storagePickerViewBottomConstraint.active = false
self.storagePickerViewTopConstraint.active = true
self.view.layoutIfNeeded()
})
}
Here's a screenshot of my storyboard...
Storyboard
You guys, I figured out a better way!
The thing that I was missing originally is that when you remove views from the superview, all the constraints are deleted. This makes things a lot easier.
I kept 1 constraint class variable for the view that is currently being shown, so the view can animate down out of place.
I also added a "setup" function to get the view in place on viewdidload(). Without this the view will always animate in from the top the first time it appears (couldn't figure out a way around this).
Here's what I used:
var secondaryMenuBottomConstraint: NSLayoutConstraint!
func setupSecondaryMenu(secondaryMenu: UIView){
secondaryMenu.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = secondaryMenu.topAnchor.constraintEqualToAnchor(view.bottomAnchor)
let leftConstraint = secondaryMenu.leftAnchor.constraintEqualToAnchor(view.leftAnchor)
let rightConstraint = secondaryMenu.rightAnchor.constraintEqualToAnchor(view.rightAnchor)
view.addSubview(secondaryMenu)
NSLayoutConstraint.activateConstraints([topConstraint, leftConstraint, rightConstraint])
self.view.layoutIfNeeded()
secondaryMenu.removeFromSuperview()
}
func showSecondaryMenu(secondaryMenu: UIView){
//Close any open keyboards
amountTextField.resignFirstResponder()
notesTextField.resignFirstResponder()
//Add the view and then add constraints to show the submenu below the current view
secondaryMenu.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = secondaryMenu.topAnchor.constraintEqualToAnchor(view.bottomAnchor)
let leftConstraint = secondaryMenu.leftAnchor.constraintEqualToAnchor(view.leftAnchor)
let rightConstraint = secondaryMenu.rightAnchor.constraintEqualToAnchor(view.rightAnchor)
view.addSubview(secondaryMenu)
NSLayoutConstraint.activateConstraints([topConstraint, leftConstraint, rightConstraint])
secondaryMenuBottomConstraint = secondaryMenu.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
//animate the view up into place
UIView.animateWithDuration(0.4, animations: {
topConstraint.active = false
self.secondaryMenuBottomConstraint.active = true
self.view.layoutIfNeeded()
})
}
func dismissSecondaryMenu(secondaryMenu: UIView){
if secondaryMenu.superview != nil {
secondaryMenu.translatesAutoresizingMaskIntoConstraints = false
let secondaryMenuTopConstraint = secondaryMenu.topAnchor.constraintEqualToAnchor(view.bottomAnchor)
self.secondaryMenuBottomConstraint.active = false
UIView.animateWithDuration(0.4, animations: {
secondaryMenuTopConstraint.active = true
self.view.layoutIfNeeded()
}, completion: {(finished:Bool) in
secondaryMenu.removeFromSuperview()
})
}
}