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)
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?
I want to display some UI elements, like a search bar, on top of my app's first VC, and also on top of a second VC that it presents.
My solution for this was to create a ContainerViewController, which calls addChildViewController(firstViewController), and view.addSubview(firstViewController.view). And then view.addSubview(searchBarView), and similar for each of the UI elements.
At some point later, FirstViewController may call present(secondViewController), and ideally that slides up onto screen with my search bar and other elements still appearing on top of both view controllers.
Instead, secondViewController is presented on top of ContainerViewController, thus hiding the search bar.
I also want, when a user taps on the search bar, for ContainerViewController to present SearchVC, on top of everything. For that, it's straightforward - containerVC.present(searchVC).
How can I get this hierarchy to work properly?
If I understand correctly, your question is how to present a view controller on top (and within the bounds) of a child view controller which may have a different frame than the bounds of the parent view. That is possible by setting modalPresentationStyle property of the view controller you want to present to .overCurrentContext and setting definesPresentationContext of your child view controller to true.
Here's a quick example showing how it would work in practice:
override func viewDidLoad() {
super.viewDidLoad()
let childViewController = UIViewController()
childViewController.view.backgroundColor = .yellow
childViewController.view.translatesAutoresizingMaskIntoConstraints = true
childViewController.view.frame = view.bounds.insetBy(dx: 60, dy: 60)
view.addSubview(childViewController.view)
addChildViewController(childViewController)
childViewController.didMove(toParentViewController: self)
// Wait a bit...
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
let viewControllerToPresent = UIViewController()
viewControllerToPresent.modalPresentationStyle = .overCurrentContext // sets to show itself over current context
viewControllerToPresent.view.backgroundColor = .red
childViewController.definesPresentationContext = true // defines itself as current context
childViewController.present(viewControllerToPresent, animated: true, completion: nil)
}
}
There is a library I really enjoy that you could find here (https://github.com/pixyzehn/MediumMenu).
Essentially it does the following:
The menu in the back is actually a UIViewController. If we delve into the source code, we'll find the following very important code:
public init(items: [MediumMenuItem], forViewController: UIViewController) {
self.init()
self.items = items
height = screenHeight - 80 // auto-calculate initial height based on screen size
frame = CGRect(x: 0, y: 0, width: screenWidth, height: height)
contentController = forViewController
menuContentTableView = UITableView(frame: frame)
menuContentTableView?.delegate = self
menuContentTableView?.dataSource = self
menuContentTableView?.showsVerticalScrollIndicator = false
menuContentTableView?.separatorColor = UIColor.clearColor()
menuContentTableView?.backgroundColor = menuBackgroundColor
addSubview(menuContentTableView!)
if panGestureEnable {
let pan = UIPanGestureRecognizer(target: self, action: #selector(MediumMenu.didPan(_:)))
contentController?.view.addGestureRecognizer(pan)
}
let menuController = UIViewController()
menuController.view = self
UIApplication.sharedApplication().delegate?.window??.rootViewController = contentController
UIApplication.sharedApplication().delegate?.window??.insertSubview(menuController.view, atIndex: 0)
}
The first few lines aren't really important, but the very last lines (the ones dealing with the UIApplication.sharedApplication().delegate?.window?? are the key to this library working, but they also limit the library.
According to those lines, we're making the UIViewController that we want the menu for to be the rootViewController and then we add the menu view to the window at the 0 index (I'm guessing this is what puts it in the back, behind the contentController).
The problem with the library:
The library only works if you want the menu on the initial view controller. In a sense, only if the contentController is already the rootViewController. It'll actually function for me in my app, but I can't segue back to my original UIViewController because it isn't in the hierarchy anymore.
I have a scenario where I have a Login View Controller, and when you successfully login, I segue you to my UINavigationController on which I want the menu. The first clear sign of an issue is that I get the complaint "Warning: Attempt to present UINavigationController on LoginViewController whose view is not in the window hierarchy!" Obviously, it isn't in the window hierarchy because I'm reassigning the rootViewController.
I don't know how to fix this. Is there a way to have this functionality work when the UIViewController I want the menu on isn't the initial view controller?
Short answer is: Yes.
This can be implemented with by assigning a UIViewControllerTransitioningDelegate to the ViewController containing the list-menu and implementing the interactionControllerForPresentation() and interactionControllerForDismissal() methods. You don't have to touch the UIWindow (directly).
You could just change this part about the existing library, but from the looks of it it's just a TableView with a fancy transition. You could easily implement this yourself.
Check out Apple's API Reference on interactive transitions and this tutorial by Ray Wenderlich for a hands on example.
So it is clear your scenario is:
rootViewController = a UINavigationController
the rootViewController of UINavigationController is the LoginController
When someone login succeed, the navigationController push(or segue) to a menuController
or
rootViewController is the LoginController
When someone login succeed, it will present the menuController
So the key point is to get a menuController and not set it to the rootViewController of the app.
Here is the code which i made some change to fit your requirement.
https://github.com/Moonsownner/MediumMenu-master
What did i do:
MediumMenu actually is a UIView, we have to create a controller to contain it so that to make the transition. So i make a new class named MediumMenuController. And you can see i move function showMenu into MediumMenuController. So if someone want to show the menu manually, the target must be a MediumMenuController not the previous NavigationController. And the MediumMenuController will contain your business controller which you send in into the init function of MediumMenuController.
class MediumMenuController: UIViewController {
let menuView: MediumMenu
let child: UIViewController
init(items: [MediumMenuItem], childController: UIViewController){
self.menuView = MediumMenu(items: items, forViewController: childController)
self.child = childController
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(menuView)
addChildViewController(child)
self.view.addSubview(child.view)
child.view.frame = UIScreen.mainScreen().bounds
child.didMoveToParentViewController(self)
}
func showMenu() {
menuView.show()
}
}
I delete the code in MediumMenu.
let menuController = UIViewController(); menuController.view = self
UIApplication.sharedApplication().delegate?.window??.rootViewController
= contentController UIApplication.sharedApplication().delegate?.window??.insertSubview(menuController.view,
atIndex: 0)
and create a new controller named MediumMenuController to add the MediumMenu and the other controllers views to it.
Maybe it is not clear by saying above. The code can show you the detail. Good luck.
You can achieve it by following steps.
(i) Make LoginViewController as your rootViewController.
(ii) When user signs in successfully, redirect user to Home Screen. That is your first screen that you want to show after successfully logged in.
(iii) You can make a parent class of UIViewControllers where you want to show the menu.
(iv) After that, when you initialize any UIViewController, parent class will call public init(items: [MediumMenuItem], forViewController: UIViewController) method and in that you can replace last two lines with below code.
self.view.insertSubview(menuController.view, atIndex: 0)
(v) On tapping sign out button, you can simply dismiss presented view controller, so LoginViewController will always be there.
Hope this helps!
I have a UISplitViewController for Master/Detail functionality on an iPad. The master view controller shows a list of items, and when the user selects one the detail information is shown in the detail view controller. I have the detail view controller set to navigate to another view controller to display a graph. When this happens, I hide the primary view controller with the following lines in my prepareForSeque.
if let svc = self.splitViewController {
svc.preferredDisplayMode = .PrimaryHidden
}
This works great. When navigating back to the detail view from the graph view I would like to again show the primary view from the split view controller. I put this in viewWillAppear.
guard let svc = self.splitViewController else { return }
if svc.preferredDisplayMode != .Automatic {
svc.preferredDisplayMode = .Automatic
}
Again this works exactly as I would expect. The problem is the detail view changes size during this process and it is not laid out properly when returning from the graph view.
Here is a screen shot before navigating to the graph view, and before hiding the primary view of the UISplitViewController.
And this is after returning from the graph view.
My attempt to fix the issue was to force the detail view to layout itself. I tried the following:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let svc = self.splitViewController else { return }
let detailNavController = svc.viewControllers[svc.viewControllers.count-1] as! UINavigationController
detailNavController.view.setNeedsLayout()
}
It sort of works but causes an ugly jump in the interface as the detail view appears and then a second later is relaid out. Is there a better way to get the view laid out properly before it is displayed?
I got it working, and figured I would share in case anybody else runs into the same issue. As Tim stated in the comments, it is a matter of running layout early enough in the chain to get things laid out before the view is presented on screen.
I did the following in the Graph view controller scene:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
guard let svc = self.splitViewController else { return }
svc.preferredDisplayMode = .Automatic
}
This set the split view back to showing both the primary and secondary view controllers very early in the process.
The place where I was going wrong was the parent of my detail view controller is a UINavigationController. I assumed that since this was being sized wrong, I needed to get it to lay itself out again after setting the split view controller to show the primary and secondary views. This was a wrong assumption. What ended up working was going up one more level to the UISplitviewController and having that perform a layout on itself.
In my detail view controller I did the following:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
guard let svc = self.splitViewController else { return }
svc.view.setNeedsLayout()
svc.view.layoutIfNeeded()
}
This was early enough in the chain that all the layout gets completed before the view is shown, but late enough that the split view controller has the appropriate sizing information for showing both the primary and secondary views.
Hopefully this helps someone else, and thanks for the comments.
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)