18

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.

See lines

Image1: For some reason I can see the lines threw my view.

Hidden true

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 ();
    }
jonas vermeulen
  • 1,235
  • 5
  • 23
  • 40
Mittchel
  • 1,896
  • 3
  • 19
  • 37
  • possible duplicate of [How to add a UIView above the current UITableViewController](http://stackoverflow.com/questions/4641879/how-to-add-a-uiview-above-the-current-uitableviewcontroller) – Senseful Jul 10 '14 at 23:31
  • @Mittchel http://stackoverflow.com/a/26138644/1856497 – Victor Oct 01 '14 at 17:17

7 Answers7

52

You can use self.navigationController.view as view for adding subview.

sagus_helgy
  • 1,417
  • 1
  • 18
  • 30
  • 1
    This did the job for me~ Thank you:) – DeXter Apr 08 '15 at 08:39
  • You can create a custom subview like this: let customView: UIView = { let //addPropertiesHere customView = UIView() return customView }() self.navigationController?.view.addSubview(customView). Works really well with DZNEmptyDataSet. – Aaron Zheng Jun 13 '17 at 03:20
  • This will cover the navigation bar (iOS 13), which is not what the OP wants. – Womble May 18 '20 at 09:04
40

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.

redent84
  • 18,901
  • 4
  • 62
  • 85
  • 3
    you lose so much functionality by not using UITableViewController so best choose another solution – malhal Jan 13 '19 at 22:28
3

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];
jonas vermeulen
  • 1,235
  • 5
  • 23
  • 40
2
UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];
jianpx
  • 3,190
  • 1
  • 30
  • 26
2

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)

2

3

Community
  • 1
  • 1
Tommie C.
  • 12,895
  • 5
  • 82
  • 100
1

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)
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
0

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

        }

    }
PatricioS
  • 111
  • 8