I have created a parallax type scroll effect (or stretchy header) in my view controller.
I have a uiscrollview, which is anchored to the windows margins. Inside of this I have a view, that is anchored with 0 constant constraints to the scroll view, and set to equal widths.
Inside that view is the content. At the top of the content is an image, and this is anchored to the top of the window (safe area guide) using two constraints.
First is constant=0 priority=750.
Second is constant<=0 priority=1000.
This works.. when i scroll up everything scrolls up. When i scroll down from the top, the image stretches and then bounces back.
Question: I am trying to also implement the navigation bar to hide when user scrolls. I have added this code to the view controller:
override func viewDidAppear(_ animated: Bool) {
navigationController?.hidesBarsOnSwipe = true
}
This works ok when I scroll up (the nav bar fades and animates up until hidden), but when i scroll back down the navigation bar doesn't return. I assume it is hidden somehow by the image that is anchored to the top of the window. But how can I adjust/reattach the navigation bar?
xcode 9 - swift 4
Try with below code might be help.
Make sure you should take delegate of UIScrollView and implement scrollViewDidScroll delegate method.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollOffset: Float = Float(scrollView.contentOffset.y)
if scrollOffset < 0 {
navigationController?.hidesBarsOnSwipe = false
navigationController?.setNavigationBarHidden(false, animated: true)
}
else {
navigationController?.hidesBarsOnSwipe = true
}
}
Related
My setup is a UITabBarController with two ViewControllers connected.
setup
On one of the ViewControllers I've added a ContainerView constrained to the tab bar with a Blue Subview and a button on a subview. Constraints are here
constraints
Then I add the containerView to keyWindow to show it on top of other ViewControllers like this:
containerView!.translatesAutoresizingMaskIntoConstraints = false
UIApplication.shared.keyWindow?.addSubview(containerView!)
After adding the containerView to the keyWindow I press the button on a Blue view and the view jumps on top of the screen. What am I missing here?
What button does:
#IBAction func changeColor(_ sender: Any) {
blueView.backgroundColor = UIColor.darkGray
}
NOTES: If I set a button picture to project asset and not system asset the view doesn't jump on top. assets
Also, if I don't add the view to the keyWindow the view doesn't jump on top with whatever asset.
In here like I see you use autolayout for constraint. So you need to remove translatesAutoresizingMaskIntoConstraints to get the contsraints working.
So you need to remove this line
// remove this
// containerView!.translatesAutoresizingMaskIntoConstraints = false
So I want to create a slide out menu. I want the menu to slide out when a tab bar item/button is touched.
So far, I have created a tab view controller with 4 different tab bar buttons. Each button leads to a different view controller and each view controller has been separated into their own storyboard.
I made an attempt of adding a UIViewController for the side menu to the class that is already established as a UITabBarController and I got the error:
Multiple inheritance from classes 'UITabBarController' and 'UIViewController'.
Is there a way around this?
I appreciate all the help
Apps tend to have a top level container like a Tab Bar Controller that cannot be embedded inside a View. The approach here is to wrap the main body and the left menu each inside a Container View. Now both of these elements can be arranged inside a wrapper View Controller.
A Scroll View is used to simulate opening and closing the menu by shifting the left menu on/off-screen.
Containing View Controller
Drop a View Controller onto the Storyboard. This is your entry point into the app.
Check the box for Is Initial View Controller.
Change the Simulated Size from Fixed to Freeform. Set the Width to 568 so we can fit the menu and Tab Bar side-by-side.
Create a new Swift file and set this View Controller's class to ContainerVC.
import UIKit
class ContainerVC : UIViewController {
}
Scroll View
Inside Container View Controller, drop in a Scroll View and add constraints in all directions.
Check the box for Scrolling Enabled. This allows you to pan the screen to slide the menu. You might need to disable this if your app uses horizontally scrolling elements.
Check the box for Paging Enabled. This snaps the menu to either the open or closed state.
Uncheck the box for Bounces. You don't really want to scroll past the right edge of the Tab Bar Controller.
Wire the IBOutlet to ContainerVC:
#IBOutlet weak var scrollView: UIScrollView!
Left Container
The left container holds the menu, and is not quite the full width of the screen.
Drag a Container View into the left side of the Scroll View.
Add constraints for top, left, and right to the containing Scroll View.
Hard-code the width to 260.
Add a constraint for Equal height with the ContainerVC's embedded View. Note: don't constrain the height to the Scroll View.
Delete the embedded View Controller that came with the Container View.
Drop a new Table View Controller(Any view according to your need) onto the Storyboard, and connect using an embed segue.
Right Container
The right container holds the main body of your app, namely the Tab Bar Controller.
Drag a second Container View into the right side of the Scroll View.
Add constraints to the containing Scroll View for top, right, and bottom. Connect horizontally to the left Container View you created earlier.
Constraint both Equal height and Equal width to the ContainerVC's embedded View.
Note: do not constrain these to the Scroll View.
Again, delete the embedded View Controller that came free with the Container View. Instead, create an embed segue to the Tab Bar Controller.
To create a little visual separation between the two containers, add a Runtime Attribute to the Right Container. Add layer.shadowOpacity with a number of 0.8.
Tabs
Embed each tab inside a Navigation Controller. Doing so gives you a free Navigation Bar.
Drag a Bar Button Item into the top left corner of each Navigation Bar.
Wire an IBAction to each controller. These will fire off a Notification to the great-grandfather ContainerVC to toggle the menu.
#IBAction func toggleMenu(sender: AnyObject) {
NotificationCenter.default().post(name: Notification.Name("toggleMenu"), object: nil)
}
Finally add the below code into ContainerVC:
class ContainerVC : UIViewController {
// This value matches the left menu's width in the Storyboard
let leftMenuWidth:CGFloat = 260
// Need a handle to the scrollView to open and close the menu
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
// Initially close menu programmatically. This needs to be done on the main thread initially in order to work.
DispatchQueue.main.async() {
self.closeMenu(animated: false)
}
// Tab bar controller's child pages have a top-left button toggles the menu
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.toggleMenu), name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.closeMenuViaNotification), name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)
// Close the menu when the device rotates
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
// LeftMenu sends openModalWindow
NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.openModalWindow), name: NSNotification.Name(rawValue: "openModalWindow"), object: nil)
}
// Cleanup notifications added in viewDidLoad
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func openModalWindow() {
performSegue(withIdentifier: "openModalWindow", sender: nil)
}
#objc func toggleMenu() {
scrollView.contentOffset.x == 0 ? closeMenu() : openMenu()
}
// This wrapper function is necessary because closeMenu params do not match up with Notification
#objc func closeMenuViaNotification(){
closeMenu()
}
// Use scrollview content offset-x to slide the menu.
func closeMenu(animated:Bool = true){
scrollView.setContentOffset(CGPoint(x: leftMenuWidth, y: 0), animated: animated)
}
// Open is the natural state of the menu because of how the storyboard is setup.
func openMenu(){
print("opening menu")
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
#objc func rotated(){
if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
DispatchQueue.main.async() {
print("closing menu on rotate")
self.closeMenu()
}
}
}
}
extension ContainerVC : UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollView.contentOffset.x:: \(scrollView.contentOffset.x)")
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollView.isPagingEnabled = true
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollView.isPagingEnabled = false
}
}
The code accomplishes the following:
Listen for the "toggleMenu" notification
Implement the toggleMenu method by opening or closing based on the
current contentOffset.x
Open and close the menu by changing the contentOffset-x.
Hopefully you have a simple slideout left menu on which to build the rest of your app.
Don't use UITabBarController, and manage tab controls using one view controller and buttons actions.
#Ajo answer is correct . embed hamburger and tabbar inside a container view .
Complete source code u can find from the link below
source code link
There is also a detailed tutorial I found
tutorial link
use initial view controller as container view .
use notifications for slide in / out
I want the green view to move forward from the container view as follows.
However, when I add a tab bar controller, the green view is cut off as follows.
I tried the following codes so that the green view is not cut off. But it did not work.
containerView.clipsToBounds = false
containerView.layer.zPosition = 100
self.view.bringSubview(toFront: containerView)
The problem seems to be not in the container view. Because when tab bar controller was added, green view started to be cut off.
When I add a tab bar controller, how can I prevent the green view from being cut off?
The problem is that UITransitionView in your UITabBarController clips all subviews. You can fix this easily if you remove clipsSubview from every subview in your TabBarController. I make this with custom TabBarController. Here is my code
class CustomTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
for item in self.view.subviews{
item.clipsToBounds = false
}
self.view.clipsToBounds = false
}
}
I added a scroll view to my view which is hover the status bar (I hid it). The scroll view is working fine, but when I'm scrolling to the top, I have a white space which disappears when I tap on my screen, and appears again when I scroll down then top.
I noticed that the scroll bar is not going to the top of my view, but stopped at the status bar.
Here are screenshots which show you what I mean.
Here I'm at the top of my view but the scroll bar isn't:
Here is the same view with the white status bar which appears when I scroll top again:
It disappear when I tap on my screen or scroll down.
Here are my constraints:
I think it's a problem of Layout Margin or something like that, but I don't what I should change?
I hide the status bar like that in my view controller:
override func viewWillAppear(_ animated: Bool) {
UIApplication.shared.keyWindow?.windowLevel = UIWindowLevelStatusBar
super.viewWillAppear(animated)
}
EDIT: Even if I comment the line which hides the status bar, I still have the same problem with my scroll view. So the problem doesn't come from how I hide it.
As Sam said, I changed the content insets to "Never" on the scroll view and it works.
While unrelated to your question, I have to react to the way you hide the status bar - the proper way is to override prefersStatusBarHidden in your view controller and call self.setNeedsStatusBarAppearanceUpdate() in your viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setNeedsStatusBarAppearanceUpdate()
}
override var prefersStatusBarHidden: Bool {
return true
}
UPDATE
Since your view controller is inside of a UINavigationViewController, you need to override childViewControllerForStatusBarHidden in UINavigationViewController to use visibleViewController as the controller to determine status bar hidden (I added override to childViewControllerForStatusBarStyle for the consistence):
extension UINavigationController {
open override var childViewControllerForStatusBarStyle: UIViewController? {
return visibleViewController
}
open override var childViewControllerForStatusBarHidden: UIViewController? {
return visibleViewController
}
}
hiding my NavigationBar with this :
func hideAndShow(){
if self.navigationController?.navigationBar.hidden == true {
self.navigationController?.setNavigationBarHidden(false, animated: true)
}else {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
}
but its also lifting up my View (maybe because View is below the Navigation), how can i hide it without lifting up my View
see the below image for better understanding
as you can see that my image in my view also get shifted up while hiding the NavigationBar any idea how can i fix this ??
can we just show the view below the layer of navigation bar ???
Two steps are required to solve your problem:
Add self.edgesForExtendedLayout = UIRectEdge.All to viewWillAppear. As a result your view will start at the top of the screen instead of below the NavigationBar. You can read more about edgesForExtendedLayout here: https://stackoverflow.com/a/19585104/1447641
Add a top constraint of {navigationbarheight} to your ImageView.
After that the position of the ImageView shouldn't be effected by the NavigationBar anymore.