Working with 2 UINavigationControllers inside a ViewController as Child causes only 1 to respond, the other does not respond - swift

I'm working with a project where I start with a ContainerViewController having 2 controllers added as a child:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
window?.rootViewController = ContainerViewController()
window?.makeKeyAndVisible()
}
The ContainerViewController:
class ContainerViewController: UIViewController {
var panGestureRecognizer: UIPanGestureRecognizer!
private var menuWidth: CGFloat = UIScreen.main.bounds.width - 50
let menuController = SideMenuViewController()
let mainController = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(panGestureRecognizer)
}
private func addChildVCs() {
addChild(menuController)
menuController.view.frame = CGRect(x: 0 - view.frame.size.width, y: 0, width: menuWidth, height: view.frame.size.height)
view.addSubview(menuController.view)
menuController.delegate = self
menuController.didMove(toParent: self)
addChild(mainController)
mainController.delegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
}
With this panGesture I just show/hide a SideMenu, nothing strange here.
Inside this MainViewController, I have a TabBarController named mainController and another UINavigationController that store user conversations, named ConversationViewController(), both added as a child like this:
class MainViewController: UIViewController {
let mainController = MainTabController()
let conversationNavigationController = UINavigationController(rootViewController: ConversationViewController())
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
}
private func addChildVCs() {
addChild(mainController)
mainController.menuDelegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
addChild(conversationNavigationController)
conversationNavigationController.view.frame = CGRect(x: view.frame.size.width, y: 0, width: view.frame.size.width, height: view.frame.size.height)
view.addSubview(conversationNavigationController.view)
conversationNavigationController.didMove(toParent: self)
}
The conversationNavigationController origin.x point is starting at the end of the mainController (tabBar) so I can panGesture to the right and show/hide this other UINavigationController.
The problem is when I start the execution I can interact with the mainController(tabBarController) but when I swipe to the right and I have the conversationNavigationController on screen, it doesn't respond to any actions.
Basically I'm working with 2 navigationControllers at the same time but I don't understand why 1 is working and the other one does not respond to any actions.
Any clue on what's the problem? I can provide more code if needed!
Thanks in advance

If you just add your child view controller's content views to your MainViewController's content view, Auto Layout doesn't know what to do with them. At the drop of a hat, Auto Layout will resize your child view controllers in unexpected and undesired ways.
I'm guessing that your view controllers' view frames are getting messed up. It might be that there is some other problem with your view controllers that's preventing them from responding to events, but first I would rule out view layout problems.
I would suggest adding container views to your main view controller, including Auto Layout constraints to put them where you want them.
Then add your child view controllers' views as subviews of those container views, and anchor all 4 edges of your child view controller's view to the edges of their parent views.
When you're first working on it, it is worth adding a borderWidth and borderColor to your different views' layers so you can see what's going on.

Related

How to present a ViewController as a Modal Sheet without the background shadow

Is there any way to present a ViewController as a Modal Sheet without the background shadow as shown in the first image below using swift. Is there an easy way or should we need to write custom UIPresentationController? [![The required output][1]][1]
[1]: https://i.stack.imgur.com/QAEEn.png![enter image description here](https://i.stack.imgur.com/q4JD5.jpg)
You can use a smaller size view controller as per your need. Firstly add a class.
class SmallSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
get {
guard let theView = containerView else {
return CGRect.zero
}
return CGRect(x: 0, y: theView.bounds.height * (281 / 896), width: theView.bounds.width, height: theView.bounds.height)
}
}
}
Then when you want to present this type of view controller just add the extension of your current view controller.
extension YourCurrentViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return SmallSizePresentationController(presentedViewController: presented, presenting: presenting)
}
}
Then present your new view controller like this
let VC = withoutShadowVC()
VC.transitioningDelegate = self
VC.modalPresentationStyle = .custom
self.present(VC, animated: true, completion: nil)
You can modify the view controller height.

How to create view controller that covers half the screen programmatically SWIFT

I am trying to create a view similar to Facebook so that when you click a button, a view controller covers half the sceeen like this:
And then if you swipe up it covers the whole view like this:
How would I go about doing this?
you should use an container view , and set frame of container view half of the screen height
. but you can just use container view in object library xcode.
container view is look like view use in your view controller class bellow the class name add this code :
class YourViewController: UIViewController {
// MARK: Properties
let containerView = UIView()
in your viewDidLayoutSubviews() function you should set frame of container view like this :
override func viewWillLayoutSubviews() {
containerView.frame = CGRect(x: 0, y: self.view.frame.midY, width: self.view.frame.width, height: self.view.frame.height / 2)
let yourSecondViewController = YourSecondViewController()
addContentContainerView(yourSecondViewController)
}
now you have a container view that cover half of the screen,
then you should add your second view controller to your container view ,
so you should create a second view controller class programmatically , or you should create a view controller in your xcode storyboard and set storyboard id for that.
for add and remove a child view controller in an container view you can use this functions:
private func addContentContainerView(_ childViewController: UIViewController) {
childViewController.willMove(toParentViewController: self)
containerView.addSubview(childViewController.view)
self.addChildViewController(childViewController)
childViewController.didMove(toParentViewController: self)
}
private func removeContentContainerView(_ childViewController: UIViewController) {
childViewController.didMove(toParentViewController: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParentViewController()
}
then you should add your second view controller to your container view with private func addContentContainerView(_ childViewController: UIViewController)
if you make the second programmatically and set you should use this code for use add that in your container in your viewWillLayoutSubviews method like this:
override func viewWillLayoutSubviews() {
containerView.frame = CGRect(x: 0, y: self.view.frame.midY, width: self.view.frame.width, height: self.view.frame.height / 2)
let yourSecondViewController = YourSecondViewController()
addContentContainerView(yourSecondViewController)
}
but if you make your second view controller in storyboard you should set id for that , select view controller then select identity inspector below identity set Storyboard ID : SecondViewController
then instead last viewWillLayoutSubwies , your viewWillLayoutSubviews should like this:
override func viewWillLayoutSubviews() {
containerView.frame = CGRect(x: 0, y: self.view.frame.midY, width: self.view.frame.width, height: self.view.frame.height / 2)
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let yourSecondViewController = mainStoryBoard.instantiateViewController(withIdentifier: "SecondViewController")
addContentContainerView(yourSecondViewController)
}
and for scroll that you should add a UIScrollView and set height of that to self.view.frame.height * 1.5

Global View in AppDelegate

I am trying to create a "global view" in the AppDelegate. So the view always shows no matter what controller you are on in the app. I built a cocoapod for this and here is very basic code that I have so far.
import UIKit
open class BetaBug: NSObject {
public var myView = UIView()
public override init() {
super.init()
}
open func show() {
if let window = UIApplication.shared.keyWindow {
myView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 50))
myView.backgroundColor = UIColor.green
window.addSubview(myView)
}
}
}
And in the AppDelegate of my project I have in didFinishLaunchingWithOptions
let a = BetaBug()
a.show()
Am I on the right track here? Is this possible?
I think you can make seperate viewcontroller and add transparent image in background also set clear color of top view in vc. Then u can add that vc's view with addsubview of rootviewcontrller. To dismiss u can use nsnotificationcenter. I am doing exactly the same.

How to modify slide transition

I had asked a question similar to this once before but I didn't realize and don't think I can use the same method due to the nuances of the two methods. (How to stop slide transition into Second VC before it covers UIView?)
I ultimately learned how to do the transition using APPCoda's lesson(http://www.appcoda.com/custom-segue-animations/)
The original question resulted in an answer providing a solution using container views and hard coded views. What I am wondering is if I can get the same effect using two separate view controllers and linking them through a segue with a gesture recognizer.
What I would like to accomplish is:
Have my initial view controller
Tap Button and have Second View Controller Overlap the first View Controller Partially (By partially I mean I have a UIView on the first View Controller that I want to remain visible. So the top of the second view controller will slide up until it hits the bottom of the UIView).
What I currently have is the original view controller being pushed up and out of the screen by the second view controller sliding up from the bottom of the screen.
Code that handles the transition using a segue from one VC to the sliding VC:
import Foundation
class CustomSegueToSecondVC: UIStoryboardSegue
{
override func perform() {
let originalVC = self.sourceViewController .view as UIView!
let slidingVC = self.destinationViewController.view as UIView!
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
slidingVC.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(slidingVC, aboveSubview: originalVC)
UIView.animateWithDuration(0.4, animations: { () -> Void in
originalVC.frame = CGRectOffset(originalVC.frame, 0.0, -screenHeight)
slidingVC.frame = CGRectOffset(slidingVC.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
}
}
};

Add view over tableview (UITableViewController)

Situation: I've got a UITableViewController loading some data asynchronously from a service. During this time I would like to place a full screen (except navigation bar) view over the table view showing my custom indicator and text.
Problem: The problem I'm facing is that when my custom view (it has a red background) is placed over the UITableView the lines of the table view are shown trough my custom view (see image below).
What I tried:
I tried to use insertBelow and above, didn't work. I also tried to do: tableview.Hidden = true, but this also hides the custom view for some reason as seen on image 2.
Image1: For some reason I can see the lines threw my view.
Image 2: Tableview + custom view gone when hidden = true used.
My code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UIView view = new UIView (new RectangleF (0, 0, this.TableView.Frame.Width, this.TableView.Frame.Height));
view.BackgroundColor = UIColor.Red;
this.TableView.AddSubview (view);
TableView.Source = new SessionTableViewSource ();
}
You can use self.navigationController.view as view for adding subview.
The issue is that the View of a UITableViewController is a UITableView, so you cannot add subviews to the controller on top of the table.
I'd recommend switching from a UITableViewController to a simple UIViewController that contains a UITableView. This way the controller main view is a plain UIView that contains a table, and you can add subviews to the main UIView and they will be placed on top of the table view.
You can try to add the view to the window instead of nesting it in the table view like this:
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: overlayview];
UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];
Swift / Storyboard Solution
Note: The code below assumes one has a custom view (ratingView in my case) that is to be presented over a UITableView.
I've read many answers to this and similar questions on SO. The other answers from these sources worked to varying degrees for me (e.g.,view loaded but not shown or not accessible,...). I am using Swift 2.0+ and I am sharing the complete solution for doing this using a UITableViewController.
Create an outlet to the Navigation Bar and the view, which you want to bring over the tableview.
//MARK:Outlets
#IBOutlet weak var navBar:UINavigationBar!
#IBOutlet var ratingView: MNGStarRating!
In my case I also wanted to animate the view over the tableview so I used a class variable to hold a reference to the inflection point and a point above the scene (off-screen).
var centerYInflection:NSLayoutConstraint!
var aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)
Then in viewDidLoad I called a function (configureRatingViewAutoLayout) which configures and adds the constraints for the new view to be animated over the tableview.
func configureRatingViewAutoLayout() {
//REQUIRED
self.navBar.superview?.addSubview(self.ratingView)
var newConstraints:[NSLayoutConstraint] = []
newConstraints.append(self.ratingView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor,constant: 10))
newConstraints.append(self.ratingView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor,constant: 10))
newConstraints.append(self.ratingView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor))
//hides the rating view above the scene
self.centerYInflection = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor, constant: self.aPointAboveScene)
//the priority must be set below 1000 if you intend to change it after it has been added to a view
self.centerYInflection.priority = 750
newConstraints.append(self.centerYInflection)
//constraints must be added to the container view of the two items
self.ratingView.superview?.addConstraints(newConstraints)
}
Nota Bene - On a UITableViewController; the self.view is the
self.tableView. They point to the same thing so I guess one could also
use the self.tableView reference above.
Sometime later... In response to a UIControl event I call this method.
#IBAction func toggleRatingView (sender:AnyObject?){
//REQUIRED
self.ratingView.superview?.layoutIfNeeded()
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.37, initialSpringVelocity: 0.99, options: [.CurveEaseOut], animations: { () -> Void in
if CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away
//I play a sound to alert the user something is happening
self.centerYInflection.constant = self.aPointAboveScene
self.centerYInflection.priority = UILayoutPriority(950)
//I disable portions of the UI
self.disableUIElements(nil)
} else {
//out of frame ~ animate in
//I play a different sound here
self.centerYInflection.constant = 0
self.centerYInflection.priority = UILayoutPriority(950)
//I enable the UI fully
self.enableUIElements(nil)
}
//REQUIRED
self.ratingView.superview?.setNeedsLayout()
self.ratingView.superview?.layoutIfNeeded()
}) { (success) -> Void in
//do something else
}
}
These helper methods can be configured to control access to elements in your scene during the presentation of the view.
func disableUIElements(sender:AnyObject?) {
//UI
}
func enableUIElements(sender:AnyObject?) {
//UI
}
Caveats
My view is a custom view in the Storyboard (sitting outside of the
tableview but connected to the TableView Controller). The view has a
required user runtime attribute defined layer.zPosition with a Number value set to 2 (this ensures that it presents in front of the
UITableView).
One could also try playing around with bringSubviewToFront:
and sendSubviewToBack: methods if you don't want to set the zPosition
(I think zPosition is simpler to use)
Try this to hook a button at bottom of the UITableViewController
declare button as a variable:
var submitButton: UIButton!
and in viewDidLoad:
submitButton = UIButton(frame: CGRect(x: 5, y: UIScreen.main.bounds.size.height - 50, width: UIScreen.main.bounds.size.width - 10, height: 50))
submitButton.backgroundColor = UIColor.init(red: 180/255, green: 40/255, blue: 56/255, alpha: 1.0)
submitButton.setTitle("Submit", for: .normal)
submitButton.titleLabel?.font = UIFont(name: "Arial", size: 15)
submitButton.titleLabel?.textColor = .white
submitButton.addTarget(self, action: #selector(submit), for: .touchUpInside)
submitButton.layer.cornerRadius = 5
self.view.addSubview(submitButton)
and implement this method:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
submitButton.frame = CGRect.init(x: submitButton.frame.origin.x, y: UIScreen.main.bounds.size.height + scrollView.contentOffset.y - 50, width: submitButton.frame.width, height: submitButton.frame.height)
}
This works for me:
if let myTopView = Bundle.main.loadNibNamed("MyTopView", owner: self, options: nil)?.first as? MyTopView {
if let view = UIApplication.shared.keyWindow{
view.addSubview(myView);
myTopView.translatesAutoresizingMaskIntoConstraints = false
myTopView.topAnchor.constraint(equalTo: view.topAnchor ).isActive = true
myTopView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myTopView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myTopView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}