Reputation: 20289
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!
Upvotes: 0
Views: 1360
Reputation: 20289
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
Upvotes: 0
Reputation: 6065
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.
Upvotes: 2