简体   繁体   中英

View is added to UIViewControllerWrapperView, but it doesn't appear correctly

I'm trying to show a loading indicator on top of a UITableViewController . Therefore I add my subview to myTableViewontroller.View.Superview which is the UIViewControllerWrapperView . This works so far, but not in all cases. If the UITableViewController is added the first time on the navigation stack, I can display the loading indicator as often as I'd like. If I pop the UITableViewController and push a new instance of it on the navigation stack again, the loading indicator doesn't appear. Successive calls make the loading indicator appear again!

I tried to setup my constraints in UpdateConstraints , LayoutSubviews , but it didn't helped. Somehow the frame seems to be zero in height. Inspecting frames shows the following:

Normal case:

parent:{X=0,Y=0,Width=320,Height=480}
hud:{X=0,Y=0,Width=0,Height=0}
parent - before setting up constraints{X=0,Y=0,Width=320,Height=480}
hud - after hud showing: {X=0,Y=0,Width=279,5,Height=0}
parent:{X=0,Y=0,Width=320,Height=480}
hud:{X=0,Y=0,Width=320,Height=480}

Failing case

parent:{X=0,Y=0,Width=320,Height=480}
hud:{X=0,Y=0,Width=0,Height=0}
parent - before setting up constraints{X=0,Y=0,Width=320,Height=480}
hud - after hud showing: {X=-20,Y=59,Width=279,5,Height=0}

So LayoutSubviews seems to called twice in normal case, because parent: and hud: line are outputted there. The -20 comes from one constraint used by me, which I get rid off in one of the next steps through using differnt constraints (limiting widht/height).

If I add a minimum height I can see the hud under the navigation bar on top left of the table view, but it is cutted off through the navigation bar and is not centered. The same behavior occurs if I try to limit the width/height between zero and my defined maximum size:

NSLayoutConstraint widthConstraint1 = NSLayoutConstraint.Create(this.hudContainer, NSLayoutAttribute.Width, NSLayoutRelation.LessThanOrEqual, this, NSLayoutAttribute.Width, 1, -40);
widthConstraint1.Priority = 750;
NSLayoutConstraint widthConstraint2 = NSLayoutConstraint.Create(this.hudContainer, NSLayoutAttribute.Width, NSLayoutRelation.GreaterThanOrEqual, null, NSLayoutAttribute.NoAttribute, 1, 0);
widthConstraint2.Priority = 1000;
NSLayoutConstraint heightConstraint1 = NSLayoutConstraint.Create(this.hudContainer, NSLayoutAttribute.Height, NSLayoutRelation.LessThanOrEqual, this, NSLayoutAttribute.Height, 1, -100);
heightConstraint1.Priority = 750;
NSLayoutConstraint heightConstraint2 = NSLayoutConstraint.Create(this.hudContainer, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, null, NSLayoutAttribute.NoAttribute, 1, 0);
heightConstraint2.Priority = 1000;
AddConstraints(new NSLayoutConstraint[] { widthConstraint1, widthConstraint2, heightConstraint1, heightConstraint2});

Here the maximum width is not respected.

This code is used by me for adding the hud as subview and setting up the main constraints:

this.TranslatesAutoresizingMaskIntoConstraints = false;
this.parentView.AddSubview(this);

NSMutableDictionary viewsDictionary = new NSMutableDictionary();
viewsDictionary["hud"] = this;

this.parentView.LayoutIfNeeded();
Console.WriteLine("parent - before setting up constraints" + this.parentView.Frame);

this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Width, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Width, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Height, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Height, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Top, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Right, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Bottom, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.Left, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.CenterX, 1, 0));
this.parentView.AddConstraint(NSLayoutConstraint.Create(this, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, this.parentView, NSLayoutAttribute.CenterY, 1, 0));

This is the code for removing and hidding the hud:

this.Alpha = 0.0f;
RemoveFromSuperview();

I'm running out of ideas. How can I solve this problem? Perhaps someone can give me a hint how can I debug this better. The code presented here is in C#, but you can provide code samples in Objective-C/Swift!

You should not manually add views to a UIViewControllerWrapperView since it's an internal API. Doing so can lead to unpredictable results.

Instead, add your loading indicator as a subview to the view property of your UITableViewController . Maybe this already solves your issue.

One more thing: if your HUD is a UIActivityIndicatorView , you don't need to remove it in order to hide it. Just set the hidesWhenStopped property to true ; then the indicator will automatically get hidden when stopAnimating is called, and will reappear when startAnimating is called.

Edit:

As you pointed out, adding the HUD to the view directly can lead to positioning issues. One way around this is to manually set a container view (a plain UIView ) which hosts the table view and the HUD:

override func viewDidLoad() {
  super.viewDidLoad()

  let tableView = self.tableView
  let containerView = UIView(frame: self.view.bounds)
  containerView.addSubview(tableView)
  containerView.addSubview(spinner)      
  view = containerView

  spinner.translatesAutoresizingMaskIntoConstraints = false
  view.addConstraint(NSLayoutConstraint(item: spinner, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0))
  view.addConstraint(NSLayoutConstraint(item: spinner, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0))

  spinner.startAnimating()
}

Note that this example uses a simple activity indicator which has an intrinsic size, so to center it only two constraints are needed; for your HUD, you may have to add more complex constraints.

I took the approach suggested by dr_barto . I defined my new base class HUDTableViewController , which is my new UITableViewController . So I'm subclassing from HUDTableViewController instead of UITableViewController .

My HUD gets the following view:

TableView.Superview (inside of my subclassed HUDTableViewController )

One would have to add additional methods and also implement such base class for UICollectionViewController . Here I'm presenting an example for UITableViewController :

public class HUDTableViewController : UIViewController, IUITableViewDataSource, IUITableViewDelegate, IDisposable
{
    private UITableView tableView;

    public UITableView TableView
    {
        get
        {
            return this.tableView;
        }
        set
        {
            this.tableView = value;
        }
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        this.tableView = new UITableView();
        this.tableView.TranslatesAutoresizingMaskIntoConstraints = false;

        this.tableView.WeakDelegate = this;
        this.tableView.WeakDataSource = this;

        UIView parentView = new UIView();
        parentView.AddSubview(this.tableView);
        View = parentView;

        NSMutableDictionary viewsDictionary = new NSMutableDictionary();
        viewsDictionary["parent"] = parentView;
        viewsDictionary["tableView"] = this.tableView;

        parentView.AddConstraints(NSLayoutConstraint.FromVisualFormat("H:|[tableView]|", (NSLayoutFormatOptions)0, null, viewsDictionary));
        parentView.AddConstraints(NSLayoutConstraint.FromVisualFormat("V:|[tableView]|", (NSLayoutFormatOptions)0, null, viewsDictionary));
    }

    [Foundation.Export("numberOfSectionsInTableView:")]
    public virtual System.nint NumberOfSections(UIKit.UITableView tableView)
    {
        throw new NotImplementedException();
    }

    public virtual System.nint RowsInSection(UIKit.UITableView tableview, System.nint section)
    {
        throw new NotImplementedException();
    }

    public virtual UIKit.UITableViewCell GetCell(UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
    {
        throw new NotImplementedException();
    }

    [Foundation.Export("tableView:didSelectRowAtIndexPath:")]
    public virtual void RowSelected(UIKit.UITableView tableView, Foundation.NSIndexPath indexPath)
    {
        throw new NotImplementedException();
    }

    [Foundation.Export("tableView:heightForHeaderInSection:")]
    public virtual System.nfloat GetHeightForHeader(UIKit.UITableView tableView, System.nint section)
    {
        throw new NotImplementedException();
    }

    [Foundation.Export("tableView:viewForHeaderInSection:")]
    public virtual UIKit.UIView GetViewForHeader(UIKit.UITableView tableView, System.nint section)
    {
        throw new NotImplementedException();
    }

    [Foundation.Export("tableView:willDisplayCell:forRowAtIndexPath:")]
    public virtual void WillDisplay(UIKit.UITableView tableView, UIKit.UITableViewCell cell, Foundation.NSIndexPath indexPath)
    {
        throw new NotImplementedException();
    }
}

As one can see I add an additional view behind the UITableView . Now I can use the superview of the table view for my HUD. Seems to work so far.

Lazy alternatives (with different behavior):

  • Use Window property:

    UIApplication.SharedApplication.Delegate.GetWindow().View

  • Use NavigationController :

    NavigationController.View

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM