My App is Navigation based Application for iPad.
First Screen is Home Screen. On Clicking button on Home Screen, it pushes to Map Screen. On the Map Screen, i have left Panel (view controller) over the map view controller which occupies 1/4 of the screen.
Left Panel is a View Controller which has Table View. On clicking cell, it should push new viewcontroller to left panel leaving the map view controller behind.
Push
Home Screen -------- Map Screen
|(Added over map screen) Push
|----- Left Panel (Table View) -------- Detail View
I can't use Split View Controller because there is a navigation in left panel as well as in Home Screen. Some times i need to animate/hide left panel. I can customise left panel.
How to implement this structure. Is it good to use Nested Navigation Controller or is there any library available. My App supports both Portrait and Landscape. I am using Swift.
I'm sorry, I don't know Swift as well. However, I think you have to declare a base layout:
you will have a MainViewController that will include a LeftPanelViewController and a FrontViewController. In the MainViewController nib, you will create the main layout using AutoLayout: add a UIView at the left of the screen and another UIView for the frontpage.
Then, link outlets and you will have the layout done! Then you have only to add/remove subviews to leftPanelView and to FrontView.
Now, I think that the right logic is that MainViewController is the NavigationController, so you have to implement the protocol of LeftPanelViewController and FrontViewController, so Main will know how and when add/remove subviews.
The important things is that no one object have to know the existence of MainViewController to preserve the logic. So you have to notify MainViewController for something, to use delegation pattern or something else as NSNotification (be aware, it could be much weight...)
I hope it will bel helpful. Bye
I would solve this by adding a ContainerView as your Left Panel. As you want to do pushes here, you can make the container view controller a navigation controller with the table view as its root view controller.
You'll still be able to animate/hide the container view just like any other view.
This extension for the UIViewController creates the side panel to the left.
private var temp_view:UIView?
private var temp_width:CGFloat?
private weak var temp_sidebar:UIViewController?
extension UIViewController {
func configureSideBar(StoryBoard:String,SideBarIdentifier:String,View:UIView){
var storyboard = UIStoryboard(name: StoryBoard, bundle: nil)
weak var sidebar = storyboard.instantiateViewControllerWithIdentifier(SideBarIdentifier) as? UIViewController
let width:CGFloat = 250//sidebar!.view.frame.size.width
let height = UIScreen.mainScreen().bounds.height
let frame = CGRectMake(0, 0, width, height)
temp_view = View
temp_width = width
temp_sidebar = sidebar
sidebar!.view.frame = frame
addChildViewController(sidebar!)
view.addSubview(sidebar!.view)
view.sendSubviewToBack(sidebar!.view)
toogleSideBarWithAnimation(0.2,Open:false)
}
func getSidebar() -> UIViewController?{
if let sdbar = temp_sidebar {
return sdbar
}
else {
println("Warning:You have tou configure sidebar first")
return nil
}
}
func toogleSideBarWithAnimation(Duration:NSTimeInterval,Open:Bool) {
if let view = temp_view {
UIView.animateWithDuration(Duration, delay: 0, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
var Frame = view.frame
if !Open{
Frame.origin.x = 0
}
else {
Frame.origin.x = temp_width!
}
view.frame = Frame
}, completion: { finished in
})
}
else {
println("Warning:You have tou configure sidebar first")
}
}
}
Let me explain how to use it.
First create a view controller scene and set the storyboard id. In this examples i used "sidebar"
Then add this instruction to the map view controller to the viewDidLoad like this
override func viewDidLoad() {
super.viewDidLoad()
self.configureSideBar("Main", SideBarIdentifier: "sidebar", View: Subview)
}
I suggest adding a UIView with all your view in it in the map view controller
To open and close it use this instruction
self.toogleSideBarWithAnimation(0.2,Open:OpenSidebar)
Related
I am using SJSegmentedViewController and loading two view controllers in SegmentController, I want to add padding 20 px to both of these controllers but I have tried but not able to achieve it,
If anyone knows how to Customise SJSegmentedViewController, please refer attached screenshot here.
override func viewDidLoad() {
super.viewDidLoad()
let headerViewController = storyboard
.instantiateViewController(withIdentifier: "HeaderViewController1")
let firstViewController = storyboard
.instantiateViewController(withIdentifier: "FirstTableViewController")
firstViewController.title = "First"
let secondViewController = storyboard
.instantiateViewController(withIdentifier: "SecondViewController")
secondViewController.title = "Second"
let segmentController = SJSegmentedViewController()
segmentController.headerViewController = headerViewController
segmentController.segmentControllers = [firstViewController,
secondViewController]
segmentController.headerViewHeight = 300.0
navigationController?.pushViewController(segmentController, animated: true)
}
Based on the implementation of SJContentView, I found that that library is not using a collection view to implement the paginated scroll which contains all view controllers. It's directly using a scroll view. The two function which handles the layout of the view controllers
func addContentView(_ view: UIView, frame: CGRect)
func updateContentControllersFrame(_ frame: CGRect)
do not provide support to add padding in between two views of two adjacent view controllers.
There are two ways, if you want to continue with SJSegmentedViewController
Add padding on your view controller's view rather wishing to have them in between views in the scroll view. Here the usable width of the screen will be reduced.
Create an issue and ask for help.
#user1374 Can you please try the following approach?
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
so my question today is how can I add a view similar to Snapchat where you use gestures to swipe to other views: left, top, bottom, right. I'm trying to use this framework but I'm not sure how to really implement it as they haven't provided a sample project. The layout of my app is I have a signup/login view controller, from there I want it to go to another view (blue) and that's the view that I want to have the different gestures mentioned above. `import UIKit
import SwipeNavigationController
class BlueViewController: UIViewController {
let orangeVC = OrangeView()
let pinkVC = PinkView()
let greenVC = GreenView()
let purpleVC = PurpleView()
override func viewDidLoad() {
let swipeNavigationController = SwipeNavigationController(centerViewController: self)
swipeNavigationController.topViewController = self.pinkVC
swipeNavigationController.bottomViewController = self.purpleVC
swipeNavigationController.leftViewController = self.greenVC
swipeNavigationController.rightViewController = self.orangeVC
view.backgroundColor = UIColor.facebookBlueColor
}
}'
I'm also not using storyboards for this project.
The SwipeNavigationController is a UIViewController and can be pushed onto a stack or presented the same as any other UIViewController. Wherever you are creating and presenting BlueViewController, you should instead create the SwipeNavigationController as the top level object that contains BlueViewController and all of the direction view controllers. BlueViewController and all of the other directions should not know anything about the SwipeViewController. BlueViewController and all of the other directions should not know anything about each other. The SwipeNavigationController is the top level view controller, all of the view controllers associated with a direction are child view controllers of it. I'm assuming that you have a navigation controller somewhere in your flow that pushes the SwipeNavigationController. In that case, you would have something like this in whatever method you want to trigger the push. I've called it nextTapped, but I'm sure it'll be something different in your code:
func nextTapped() {
let swipeNavigationController = SwipeNavigationController(centerViewController: BlueViewController())
swipeNavigationController.topViewController = PinkViewController()
swipeNavigationController.bottomViewController = PurpleViewController()
swipeNavigationController.leftViewController = GreenViewController()
swipeNavigationController.rightViewController = OrangeViewController()
navigationController?.pushViewController(swipeNavigationController, animated: true)
}
And then remove everything from viewDidLoad in BlueViewController except for the line that sets the background color. This creates the SwipeNavigationController with all of the directional view controllers, keeping the BlueViewController as the center and then pushes it onto your view controller stack. If you don't have a UINavigationController in the view controller that is displayed before the SwipeNavigationController, you can present it modally by replacing the last line with this:
present(swipeNavigationController, animated: true, completion: nil)
I would like to present a view that only occupies the bottom half of the screen, with the background view remaining visible with no possible interaction.
Just like an UIAlertContoller with the actionSheet style.
How to do that in Swift ?
Create that view as a normal UIViewController, assign an identifier to it and then instantiate it like this: (keep it as a member variable though, you will need it)
var controller = self.storyboard!.instantiateViewControllerWithIdentifier("yourIdentifier") as! UIViewController
then you can add it to your parent view like
self.addSubview(controller)
Be careful about positioning it though, you can animate it neatly to the middle of the screen like
UIView.animateWithDuration(0.5, animations: {
controller.frame.origin.y = UIScreen.mainScreen().bounds.height/2
})
in
But the initial positioning should be made the holding view's viewDidLayoutSubviews method, like
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
controller.frame.origin.y = UIScreen.mainScreen().bounds.height
}
because in viewDidLoad the views don't really have correct frames yet and it would end up in wrong position
On an iPhone project I'm using Xcode storyboard to embed a few containing views in a main scroll view. I've heard containing view is also an "embed segue". Now I don't necessarily have to embed other child controllers, I could have just created custom views and have the original child controllers' logic in those custom subviews. (I think I'm just going to do that after posting here, because it seems easier.) But I've already done the code and want to know how easy or hard it is to stay with it.
Because scroll view content is greater than the main screen bounds, it's harder to layout the container view in storyboard. I can think of three ways to solve it. I can either
Drag the scroll view up and down and put my container views there.
Just drag some view in the scroll view, and then resize the frame in the main controller's viewDidLoad. (And if I'm using auto layout then I would add auto layout there) But just seems to defy the advantage of having storyboard and embed segue in the first place. But it seems easier than #3 if I have to interact with child view controllers.
Forget storyboard and just write a Containing controller logic (as described in WWDC 2012 video Implementing UIViewController Containment) but this appears to be complicated.
Is there a way to create embed segue in Xcode, but NOT putting it in but to do something like a "manual segue" as with other view transitions? I wouldn't be able to see the layout in storyboard but at least it'll be easier than #3 and I don't have to drag up and down like #2 which seems silly.
I understand that the WWDC has an hour of video on it. But if you have watched any of their other videos it should become quite clear that time does not directly relate to complexity. This is how you use a container (or a child sub view controller) programmatically:
[self addChildViewController:child]; // 1
[self.view addSubview:child.view]; // 2
[child didMoveToParentViewController:self]; // 3
Pretty simple and only two extra lines of code compared to adding a subview. As you said, there are storyboard solutions but depending on your complexity, doing this through code may be easier. It really comes down to your preference though.
If you intend to animate adding the view, you should make the last call to didMoveToParentViewController in the completion block (i.e. after the animation has been completed).
Here are the helper functions I use to programmatically embed a child view controller in a view.
struct MyChildViewController {
static func embed(
viewControllerId: String,
storyboardName: String,
containerViewController: UIViewController,
containerView: UIView) -> UIViewController? {
guard let viewController = initViewController(viewControllerId, storyboardName: storyboardName)
else { return nil }
containerViewController.addChildViewController(viewController)
containerView.addSubview(viewController.view)
viewController.view.translatesAutoresizingMaskIntoConstraints = false
MyConstraints.fillParent(
viewController.view, parentView: containerView, margin: 0, vertically: true)
MyConstraints.fillParent(
viewController.view, parentView: containerView, margin: 0, vertically: false)
viewController.didMoveToParentViewController(containerViewController)
return viewController
}
static func initViewController(viewControllerId: String, storyboardName: String) -> UIViewController? {
let storyboard = UIStoryboard(name: storyboardName, bundle: NSBundle.mainBundle())
return storyboard.instantiateViewControllerWithIdentifier(viewControllerId)
}
}
struct MyConstraints {
static func fillParent(view: UIView, parentView: UIView, margin: CGFloat = 0,
vertically: Bool) -> [NSLayoutConstraint] {
var marginFormat = ""
if margin != 0 {
marginFormat = "-\(margin)-"
}
var format = "|\(marginFormat)[view]\(marginFormat)|"
if vertically {
format = "V:" + format
}
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(format,
options: [], metrics: nil,
views: ["view": view])
parentView.addConstraints(constraints)
return constraints
}
}
Usage:
let childWiewController = MyChildViewController.embed("MyViewControllerId", storyboardName: "MyStoryboardName", containerViewController: containerViewController, containerView: containerView)
Where:
"MyViewControllerId" - the storyboard ID of the child view controller that will be embedded.
"MyStoryboardName" - the name of the storyboard file with embedded view controller.
containerView - the view in your container view controller that will have the child view controller embedded.