I am using a UIStackView for my layout. In that stack view, when I press a button I want to hide one of the subviews. That subview contains a couple buttons and a label. My issue is that during the hide animation, the buttons and label are visible until the vertical space from the subview is fully animated away.
Is there something I can do so that when I call subview.isHidden = true, the subview`s contents hide immediately at the beginning of the animation instead of at the very end of the animation?
use a custom stackView class. Use IBOutlets in the class to reference the buttons/text and write a function that hides your outlets when self.isHidden = true. Let me know if you need more explanation.
Besides hiding the buttons and content view with an animation you could try changing the background color from clear on the views inside the stackview to the same color as the background on your view. This still might not look great but it would be better.
Obviously animation would be something like the code below but give background color on your content views in the stackview a shot.
UIView.animate(withDuration: 0.1, animations: {
//yourContentHoldingView.alpha = 0
})
Related
What I want to to: I want to drag down the whole view of a viewController to dismiss to the parent viewController using a pan gesture recognizer.
The Problem: When I drag the view down, the navigationBar decreases its height and does not look good. When the view returns to its original position, the navigationBar returns to the default size. I want the navigationBar to stay at its size. I also tried to use the new large titles and some other properties of the navigationController/-bar, but that did not solve it.
Note: Everything worked fine already before iOS 11.
My code:
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDown(_:)))
navigationController!.view.addGestureRecognizer(panGesture)
}
#IBAction func dragViewDown(_ gesture: UIPanGestureRecognizer) {
if let dragView = gesture.view {
let translation = gesture.translation(in: dragView)
dragView.center.y = (dragView.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: dragView)
}
}
This test project only has one viewController and does not provide the dismissal, but the problem is the same as in my working project.
I also uploaded the project to GitHub: https://github.com/maddinK7/navitationBar-pull-down-problem
Does anyone have an idea how to solve this? Thanks in advance.
I want the navigationBar to stay at its size
It is staying at its size. If you check the navigation bar's bounds size height before, during, and after the drag, you will see that it remains the same (probably 44) at all times. What's changing is the drawing extension that causes the drawing of the nav bar to extend up behind the status bar. It can't do that when you pull the whole thing away from the top of the screen, because it is not at the top next to the status bar any more. iOS 11 is more strict about the way it performs this drawing extension, probably because it has to do it in a special way on the iPhone X.
So, let's make sure you're doing this correctly:
Make sure that the navigation bar has a top constraint pinned to the safe area layout guide's top, with a constant of zero.
Make sure that the navigation bar has a delegate that returns .topAttached from position(forBar:).
If you are doing both those things and it doesn't help, you'll have to implement this in some other way entirely. Making the view directly draggable like this, without a custom parent view controller, was always dubious.
When UINavigationController attached top, system will add safe area top margin in the navigation background.
(NOTICE: Background margin will not changed when offset value is between 1 and 0)
So you have to handle attached/detached top event by handle gesture offset to change the right offset and content insets.
You can try the solution in my lib example. ;)
My example include UITableViewController in the UINavigationController, so it will relatively complex.
https://github.com/showang/OverlayModalViewController
I am designing an iOS app in swift, and I am having some difficulty with animations during a controller transition. Specifically, I've implemented a UINavigationControllerDelegate, to listen for when a certain view is pushed. When this view is pushed, I want to hide a bar at the bottom of the screen. My code is working almost perfectly, however whenever I begin an animation on the height of the navigation controller, the current view (which is being removed) animates its height correctly, but the new controller which is being pushed already has the new height from the animation. To put some code to it, the following function is called from my UINavigationControllerDelegate's willShow viewController function:
func animatePlayerVisibility(_ visible: Bool) {
if visible == showingPlayer {
return
}
showingPlayer = visible
let height: CGFloat = visible ? 56.0 : 0.0
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.35) {
self.playerHeight.constant = height
self.viewBottom.constant = height
self.view.layoutIfNeeded()
}
}
'playerHeight' is an IBOutlet to a constraint on the height of the player container view. 'viewBottom' is also an IBOutlet constraint between the bottom of the top container view and the bottom of the screen. Essentially, as long as these two constraints are animated together, it should look nice.
To help visualize the graphical bug, I edited this line
self.viewBottom.constant = height
to
self.viewBottom.constant = height * 2.0
I have created an imgur album of the actual wrong behavior in action:
http://imgur.com/a/znAim
As you can see, the old view controller animates properly, when the new controller already has the new animated size.
Here is the layout of my storyboard:
Any help would be really appreciated. I've been trying to fix this for a while with no success.
EDIT: The view of the animation without the *2 applied.
https://imgur.com/a/2a5Sw
Have you thought about not using UINavigationController? Maybe it will be easier to use ChildViewControllers mechanism. Then with it you can use a powerful autolayouts and have more control over animation (in your case height)
More info about this here
I've created a nice little sample project you can find here!
There are a number of things that could be going wrong, and since I haven't looked over your project personally it's likely I organized things very differently in my sample, but hopefully you will understand it. I think the big thing is that I added a constraint in storyboard to the navigationController's container to the bottom of the root viewController. I don't adjust the height of this container at all when I animate.
If I try to animate the hiding all the subviews of a stackview, I can see them moving towards the top left corner. On showing, they are animated coming from top left to their proper space.
If I hide only a subset of the arranged views, they are animated as expected.
My current workaround is to keep an invisible subview in the stack, but this is super wonky.
I am hiding via
UIView.animate(withDuration: 0.5) {
self.someStack.arrangedSubviews.forEach { $0.isHidden = !$0.isHidden
}
Try adding an additional empty view (width/height 0) into your stack view. This fixed the issue for me.
I faced a very similar problem and after a few hours of back and forth, I found that calling self.view.layoutIfNeeded() fixed the issue.
My hierarchy:
- UIView
- UIStackView
- UIStackView(1)
- UIButton
- UIButton
- UIStackView(2)
- UITextField
- UIButton
- UIActivityIndicatorView
The root UIView animates from the bottom when the keyboard appears based on a call to UITextField.becomeFirstResponder() in the (2)UIStackView. By default, every subview of (2)UIStackView is hidden. Based on a UISegmentedControl change, the app calls UITextField.becomeFirstResponder() and hides (1)UIStackView and shows (2)UIStackView. If I don't call self.view.layoutIfNeeded() after stackView.subviews.forEach { $0.isHidden = false } to show the subviews of (2)UIStackView I see them animating from the top left of the device.
I don't know if this might help you, but it might be a starting point to investigate.
What is the best approach for attaching a UIButton on top of UIScrollView or UITableView so when the view is scrolled, the button stays in its place.
Here examples below:
UIButton stays in the right bottom corner when the view is scrolled.
google+ app example
yahoo mail app example
I think this should work. Lay Out your button in a view that is outside of the tableviewcontroller. Then drag an outlet to the tableviewcontroller file. Then add it in code. This code would hold it at the top of the screen.
#IBOutlet var buttonView: UIView!
override func viewDidLayoutSubviews() {
self.view.addSubview(buttonView)
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
var rect = self.buttonView.frame
rect.origin.y = max(0,scrollView.contentOffset.y + scrollView.contentInset.top)
self.buttonView.frame = rect
}
Thank you all for great answers!
I got it worked through storyboard by moving the button from scrollView to View itself. That way it's attached on UIView and it's independent of scrollview.
storyboard snapshot
So now the structure is:
- View
- ScrollView
- Button
Before it was:
- View
- ScrollView
- Button
There are many ways to go about doing this but two that I use most often are as follows.
One approach is embedding the view controller within a navigation controller. This will set a bar on the top and bottom if you choose that you can place bar button items upon.
Another approach is to place a UIView along the top and snap the constraints to the left, right, and top with 0 no-margin. Then set the height. I usually use 40px for the height but you can use what is applicable to your needs. After that you can place a button in that UIView and then set constraints on it to keep in in place.
In my experience, this isn't reliably possible to do with the scrollView itself.
My solution is usually to put anything that needs to float above the tableView/scrollView in a plain ViewController that also contains the tableView/scrollView parent.
If you're using storyboards with a UITableViewController scene, this will likely mean you need to use another scene with UIViewController with a container that has your UITableViewController.
For UITableView use tableHeaderView. For UIScrollView you need to create a separate view not in the scroll view's hierarchy.
Another solution is to put your UIButton in a UIToolbar, and then make the toolbar a child of the UINavigationController's view. After that, in viewDidLayoutSubviews, you can set the rect of the toolbar to sit just below the navigation bar and offset the top of the UIScrollView or UITableView.
Add button which you want in the storyboard.
Design your scrollview
self.view.sendSubviewToBack(scrollViewObj)(in the code)
This worked for me.
I'm trying to position a TableView inside my ViewController view but leaving a 44 height gap between the bottom of the navigation bar and the top of the table. I then wanted to place a UITextField inside that gap to act as a stationary header. For some reason, the TableView has an empty white space above the start of the "Prototype Cells". Its just white space. Here is what it looks like in the storyboard.
When viewing the app display, this is what it looks like:
When scrolling the table, it goes all the way up to the correct place:
Try to look in the 'attribute inspector' (in the right menu) of the Participants ViewController.
Check for the option 'Extend Edges' and uncheck the 'Under Top Bars', and then relocate your tableview.
Another possible solution is to just uncheck the option 'Adjust Scroll View Insets'.
The reason is that when the viewController extends its edges, let's say under the top bar, the tableView's scrollView automatically adjusts its inset top, so that the content of the tableView will start exactly under the top bar. But in your case this is not needed, since your tableView itself starts under the bar.
Focus on the ViewController and got to the Attribute Inspector tab:
Try this one:
self.tableView.contentInset = UIEdgeInsetsMake(-65, 0, 0, 0)
Just add this in you ViewDidLoad method
self.automaticallyAdjustsScrollViewInsets = NO;
change your table view style from grouped to plain
You should not need to change the default setting for Extend Edges.
Looks like the real problem is a blank table header view in your storyboard. It's showing in the screenshot you provided of your storyboard, right below the Enter Name view and right above the Prototype Cells view. Delete it.
My issues is, I set tableHeaderView as new UIView like this
self.tableHeaderView = UIView(frame: .zero)
Remove this line, the issue is gone. Try this:
//self.tableHeaderView = UIView(frame: .zero)
This is the 2022, iOS 15 solution if anyone stumbles upon this.
if #available(iOS 15.0, *) {
UITableView.appearance().sectionHeaderTopPadding = CGFloat(0)
}
I just found a solution for this.
In my case, i was using TabBarViewController, A just uncheck the option 'Adjust Scroll View Insets'. Issues goes away.
https://i.stack.imgur.com/qgQI8.png
For Objective-C folks, add this in your ViewDidLoad method.
[self.tableView setContentInset:UIEdgeInsetsMake(-20, 0, 0, 0)];