Bring Subview Above Everything But Below Another UIView - ios

I am trying to place a UIView - popupView at the top, and another UIView (opaqueView) below popupView but above anything else. PopUpView is connected with an Outlet.
func display() {
popupView.center = CGPointMake(CGRectGetMidX(self.view.bounds), tableView.center.y);
self.view.addSubview(popupView)
popupView.clipsToBounds = true
let opaqueView = UIView()
let screenSize: CGRect = UIScreen.mainScreen().bounds
opaqueView.frame.size = CGSize(width: screenSize.width, height: screenSize.height)
opaqueView.alpha = 0.6
UIApplication.sharedApplication().keyWindow!.insertSubview(opaqueView, belowSubview: popupView)
}
Using this approach causes the opaqueView getting placed over everything including the popupView. Instead, I want to have popupView above opaqueView but keep opaqueView above everything else (view, TabBar, NavBar)

parent.insertSubview(child, belowSubview: sibling) works only when the sibling is a direct child of parent, so that child and sibling share the same parent. The current code does not work because opaqueView (the child) and popupView (the sbiling) have different parents.
That means either ① popupView should use the keyWindow as the parent, or ② opaqueView should use self.view as parent. Since you want opaqueView be above of everything, option ① is the only solution.

Related

How could a tableview.window is nil when i use tableViewController? [duplicate]

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
}
}

How to make subviews resize to UIScrollView ContentSize?

I have a UIViewController that acts as a Container View Controller. It has a UIScrollView that has the same width as the screen, but it's height is smaller than the height of the screen.
The UIScrollView contains the views of two other UIViewControllers and those views can be horizontally scrolled through.
I set my contentSize like this:
scrollView.contentSize.width = self.view.bounds.width * 2
This works and allows me to scroll through my UIViewController views horizontally.
The following is how I add the UIViewController views to my scrollView:
private func addPanel(viewController: UIViewController, panel: Panel) {
let xOffset: CGFloat!
switch panel {
case .left:
xOffset = 0.0
case .right:
xOffset = self.view.bounds.width
}
let panelView = UIView(frame: CGRect(x: xOffset, y: 0, width: self.view.bounds.width, height: self.scrollView.bounds.height))
scrollView.addSubview(panelView)
let panelViewController: UIViewController! = viewController
var viewBounds = view.bounds
viewBounds.height = panelView.bounds.height
panelViewController.view.frame = view.bounds
panelView.addSubview(panelViewController.view)
addChildViewController(panelViewController)
panelViewController.didMove(toParentViewController: self)
}
For some reason, the UIViewController views don't resize to fit the height of the UIScrollView.
Should I be doing it constraint based? How would I do this. I've been struggling with this for a few days and am at a loss.
Essentially, the UIViewController views will just like like full screen views offset and so you can't see the bottom of the views because the bottom of the screen cuts them off.
I'm just taking a guess without knowing all the code, but one reason could be if you're adding the child view controllers before the scrollview has been layouted.
After you add and set the child views sizes, the scrollview adjusts its size to the phone size but you never update the child views.
My suggestion here would be to add the child view controllers to the scrollview, but move the frame setting code into a layouting method where you know your views have the correct(visible) frames/bounds.
Given you are writing this in a view controller, one method could be -viewDidLayoutSubviews
Hope this makes sense, feel free to ask more questions if it doesn't.

UISearchController searchBar frame only resizes on first click

I have just implemented a UISearchController in a regular UIViewController, and I have a kind of weird issue.
I have a UIView that is adapted exactly to the size I want my UISearchBar to take. On first launch of the view, I add my UISearchBar as a SubView to this UIView.
But when I launch it, the UISearchBar doesn't take the size of its parent UIView -- it just takes the width of the whole screen.
As you can see, it overlaps with the button on the right.
But once I click on the search bar and cancel it, it resizes to the exact size I want.
What could be the issue here? I've tried adding AutoLayout Constraints from the SearchBar to its parent view but it doesn't seem to change anything. Doesn't [UIView addSubview:] handle this?
UISearchController's view behaviour is weird indeed, but there is a simple workaround.
Use an additional container view
put UISearchController's view inside
set UISearchController's view autoresizing mask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
set UISearchController's frame to container's view bounds
At this point UISearchController.view won't resize beyond container.bounds.
For me this didn't work, but I found a solution that I would like to share:
Instead of putting the searchBar in a containerView, put a navigationBar in the containerView, and put the searchBar in this navigationBar. For me, the problem still exists at this point.
But then I put a 1-pixel wide view as a navigationItem to the right of the navigationBar. From then it works all fine, the textfield of the searchBar didn't stretch anymore after the first selection.
It is more of a hack instead of a good solution to an annoying bug(?), but for me this hack is acceptable as I already needed some margins on both side of the searchBar. Here is some code:
//on init or viewDidLoad
navigationBar = UINavigationBar(frame: .zero)
let navigationItem = UINavigationItem()
navigationItem.titleView = searchController.searchBar
let leftView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: Design.margin, height: 1))
leftView.backgroundColor = .clear
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: leftView)
let rightView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: Design.margin, height: 1))
rightView.backgroundColor = .clear
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightView)
navigationBar.items = [navigationItem]
containerView.addSubview(navigationBar)
// setup frame sizes in your layout

Set the ViewController on UIView programmatically

Is it possible to set the viewController for an UIView programmatically? What I want to do is make a UIView that always covers 50% of the screen's height and add a ViewController on that view after initializing it. I can't seem to find a method. Pseudocode:
let view = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height UIScreen.main.bounds.height * 0.5)
//Can't seem to find a method that does exactly this:
view.setViewController(.......)
Insert yourAnotherVC.view as a subview to yourview
var MenuView: AnotherViewController? = (self.storyboard?.instantiateViewController(withIdentifier: "NewsView") as? AnotherViewController)
self.addChildViewController(MenuView)
MenuView?.didMove(toParentViewController: self)
MenuView?.view?.frame = CGRect(x: CGFloat(0.0), y: CGFloat(self.containerView.frame.size.height), width: CGFloat(self.containerView.frame.size.width), height: CGFloat(self.containerView.frame.size.height))
yourview.insertSubview(MenuView?.view, belowSubview: self.bottomMenuView)
//Show presentation logic here forward i mean show Anotherviewvc bottom to top or top to bottom
I'll hide and show views instead of switching out the whole UIViews so no more answers needed #close
// Add Child View Controller from your MainVC
addChildViewController(viewController) //your 2nd VC
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds //here you make it 50% of height!
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
You can add a Container View, that uses its own UIViewController. Simply drag it from the object library and you are good to go.

Swift popover in scrollview

i have a scrollview with some subviews. Inside this subviews i have other subviews.
i want to present a popover that is anchored to one of this "nested" subviews.
I correctly show the popover, and it is anchored to the corrected subview, but whenever i scroll the scrollview, the popover doesn't move with the scrollview.
I would like my popover to move and to adjust its "x" origin every time i scroll the scrollview horizontally.
This is the code i use to present the popover.
func showAlarmViewController(notification: NSNotification){
troubleViewController = TroubleshootViewController()
troubleViewController!.modalPresentationStyle = .Popover
troubleViewController!.preferredContentSize = CGSizeMake(300.0, 150.0)
popoverMenuViewController = troubleViewController!.popoverPresentationController
popoverMenuViewController!.permittedArrowDirections = .Down
popoverMenuViewController!.delegate = self
popoverMenuViewController!.passthroughViews = [self.detailScrollView]
popoverMenuViewController!.sourceView = (notification.object as! UIView).superview
popoverMenuViewController!.sourceRect = CGRect(x: -100, y: 100, width: 300, height: 150)
presentViewController(
troubleViewController!,
animated: true,
completion: nil)
}
any help please?
thanks!
You have to add your popoverMenuViewController in one of the nested subviews of scrollview or in scrollview by
scrollview.addsubview(popoverMenuViewController)
If popoverMenuViewController controller is UIViewController, then add container view in scrollview and link that container view to popoverMenuViewController. later you can add container view as a subview of scrollview.
here, is the link how container view can be implemented. How to use a 'Container View' in iOS?
And
http://spin.atomicobject.com/2015/07/21/ios-container-views/
or add popoverMenuViewController in one of the nested subview of scrollview
subviewofscrollview.addsubview(popoverMenuViewController)
Set your popoverMenuViewController anchor according to the subview content size, where you added this popoverMenuViewController.

Resources