In one of my screens I need to add UIView (with some labels and buttons) to DialogViewController. The reason is I'm not using TableView header for this is that I don't want this view to scroll when table is scrolled.
I can achieved this if I add my custom view to navigation bar, but then my view won't receive any touches (navigation controller eats them).
I've also tried adding custom view to DialogsViewController parent controller and while it works, resizing the frame of tableview in LoadView() doesn't do anything.
Is there any other ways of adding custom view to DialogViewController?
Thank you.
To add a header that doesn't scroll you could create a controller whose view contains both the additional views you want to add as well as the DialogViewController's view. For example the following simple example adds a UILabel along with the DialogViewController's view as subviews of an additional controller (called container in this case):
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
MyDialogViewController dvc;
UIViewController container;
float labelHeight = 30;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
container = new UIViewController ();
container.View.AddSubview (new UILabel (new RectangleF (0, 0, UIScreen.MainScreen.Bounds.Width, labelHeight)){
Text = "my label", BackgroundColor = UIColor.Green});
dvc = new MyDialogViewController (labelHeight);
container.View.AddSubview (dvc.TableView);
window.RootViewController = container;
window.MakeKeyAndVisible ();
return true;
}
}
Then the DialogViewController adjusts the TableView's height in the ViewDidLoad method:
public partial class MyDialogViewController : DialogViewController
{
float labelHeight;
public MyDialogViewController (float labelHeight) : base (UITableViewStyle.Grouped, null)
{
this.labelHeight = labelHeight;
Root = new RootElement ("MyDialogViewController") {
new Section (){
new StringElement ("one"),
new StringElement ("two"),
new StringElement ("three")
}
};
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
TableView.Frame = new RectangleF (TableView.Frame.Left, TableView.Frame.Top + labelHeight, TableView.Frame.Width, TableView.Frame.Height - labelHeight);
}
}
Here's a screenshot showing the result in the simulator:
Related
Currently my tab bar controller is at the bottom of the view controller. I was wondering if there is a way to move it to the top of the view controller as I cant seem to find any documentation on it.
Swift 3
let rect = self.tabBar.frame;
self.tabBar.frame = rect.insetBy(dx: 0, dy: -view.frame.height + self.tabBar.frame.height + (self.navigationController?.navigationBar.frame.height)!)
Swift 5 | Xcode 11
Try overriding viewWillLayoutSubviews() in your class that extends UITabBarController and then put following:-
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tabBar.invalidateIntrinsicContentSize()
var tabSize: CGFloat = 44.0;
var orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
if (orientation.isLandscape) {
tabSize = 32.0;
}
var tabFrame: CGRect = self.tabBar.frame;
tabFrame.size.height = tabSize;
tabFrame.origin.y = self.view.frame.origin.y;
self.tabBar.frame = tabFrame;
// Set the translucent property to false then back to true to
// force the UITabBar to reblur, otherwise part of the
// new frame will be completely transparent if we rotate
// from a landscape orientation to a portrait orientation.
self.tabBar.isTranslucent = false;
self.tabBar.isTranslucent = true;
}
Reference: https://stackoverflow.com/a/29580094/6117565
You can't change position on UITabbar. Read Apple documentation for tabbar.
If you want to set effect like tabbar on top of viewController then You can manage that by using one uiview of same size of tabbar and multiple uibuttons in that view which works as tabs. And set that view at top or any position at which you want to show tabbar. You have to manage viewcontrollers displays on button click and manage that view to every view controller. So it is very difficult task, so it is better to use default tabbarController provided by system.
You can try using UIViewController instead of UITabBarController and then place TabBar control on top in your view by constraints.
Another way is create your own tabbar. (:
Calling setView on a UIViewController removes the current view from its superview automatically. I couldn't find this documented. In my case I want to dynamically swap a UIViewController object for another while maintaining my view structure. I was planning to just relink the view to the new controller but alas, that doesn't work.
In general, automatically removing the view from its superview seems like a sensible decision. The documentation should reflect this though.
(For anyone thinking it's a really bad idea to swap a view controller object in this way, let me add that the controller I'm swapping in is a subclass of the existing controller. And this method is great for adding functionality to a view that is an extension of another.)
This is how I solved it:
UIView *viewToKeep = self.viewController.view;
UIView *superview = viewToKeep.superview;
self.viewController.view = nil; // removes the view from its superview
UIViewController *swapInViewController = [[UIViewController alloc] init];
swapInViewController.view = viewToKeep;
[superview addSubview: viewToKeep];
[viewToKeep applyConstraintsToFillSuperview]; // a helper to add auto layout constraints that make the view always fill it's parent
self.viewController = swapInViewController;
Doing this is definitely going against the grain in UIKit, and therefore very likely a bad idea.
That said, here's something which prevents the view from being automatically removed, which means you don't need to use addSubview to put it back where it was.
Only tested as far as you see below, caveat emptor, etc. In Swift:
class View: UIView {
var preventRemovalFromSuperView = false
override func removeFromSuperview() {
if !preventRemovalFromSuperView {
super.removeFromSuperview()
}
preventRemovalFromSuperView = false
}
}
let vc1 = UIViewController()
let vc2 = UIViewController()
let sv = UIView()
let v = View()
// Existing hierarchy
sv.addSubview(v)
vc1.view = v
// Swap view controllers
v.preventRemovalFromSuperView = true // Prevent automatic removal
vc1.view = nil // Prevent UIViewControllerHierarchyInconsistency exception
vc2.view = v
// Check that view was not automatically removed
v.isDescendantOfView(sv) // true
I currently have a UIViewController which contains a menu bar and container UIView.
On main view load the container has an embed segue with a separate main content UIViewController.
When I click a button in the menu bar I have a custom segue set up to load a UITableViewController into the UIView container using the following code
-(void) perform {
BlockDetailViewController *src = (BlockDetailViewController *)[self sourceViewController];
UIViewController *dst = (UIViewController *)[self destinationViewController];
//Clear view of all subviews
for (UIView *view in src.viewContainer.subviews) {
[view removeFromSuperview];
}
//save destination
src.currentViewController = dst;
//set placeholderOutlet to destination
[src.viewContainer addSubview:dst.view];
}
This loads the UITableViewController into the UIView fine, however, it seems the UITableViewController size is set as the default window size which means the bottom cells of the UITableViewController are hidden from view. You can see them if you drag up but when you let go the bounce takes them out of the view.
How do I make sure the size of the UITableViewController is the same size as the UIView it is being placed into?
Thanks
You just have to create a a CGRect (frame) and set it to the views frame:
dst.view.frame = CGRectMake( CGRectGetMinX(self.view.frame),
CGRectGetMinY(self.view.frame),
CGRectGetWidth(self.view.frame),
CGRectGetHeight(self.view.frame);
the values should be float values. This is just an example.
When you define dst, you can use CGRect to set its position, width and height.
I am making an application that is a tab-view controller. For the first tab, I have a UIPageViewController that is scrolling through pictures. Everything works properly, except for that the picture is not showing full screen. I have constraints set up for the image view to cover the whole view controller but when it gets loaded into the page view, it doesn't cover all the way to the bottom. The image gets cut off by the scrolling dot indicators and then it is just white.
I'm sure it is simple, but is there a way that the images will cover the full screen like I have the constraints set up to do?
There's quite a simple solution for this problem on that page: http://tunesoftware.com/?p=3363
It is done by overriding the viewDidLayoutSubviews method of UIPageViewController and basically doing two things:
Increasing the bounds of the UIScrollView (the content) inside the UIPageViewController by setting it's frame to the bounds of the page controller's view
Moving the UIPageControl element (the dots) to the front of the view hierarchy so that it's in front of the content
Here's the code he provides (in case the link gets broken):
import UIKit
class TSPageViewController: UIPageViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var subViews: NSArray = view.subviews
var scrollView: UIScrollView? = nil
var pageControl: UIPageControl? = nil
for view in subViews {
if view.isKindOfClass(UIScrollView) {
scrollView = view as? UIScrollView
}
else if view.isKindOfClass(UIPageControl) {
pageControl = view as? UIPageControl
}
}
if (scrollView != nil && pageControl != nil) {
scrollView?.frame = view.bounds
view.bringSubviewToFront(pageControl!)
}
}
}
All you have to do after that, is:
If you set your page controller in the storyboard, then just go to the identity inspector pane and change its class to TSPageViewController
If you set your page controller in code, then just make sure the class you defined for it inherits the TSPageViewController class.
Here's the Objective-C version. Create a new class (2 files):
(1) BasePageViewController.h extending UIPageViewController
#import <UIKit/UIKit.h>
#interface BasePageViewController : UIPageViewController
#end
(2) BasePageViewController.m containing viewDidLayoutSubviews override
#import "BasePageViewController.h"
#implementation BasePageViewController
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
UIScrollView *scrollView = nil;
UIPageControl *pageControl = nil;
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UIScrollView class]])
{
scrollView = (UIScrollView *)view;
}else if([view isKindOfClass:[UIPageControl class]])
{
pageControl = (UIPageControl *)view;
}
}
if(scrollView != nil && pageControl!=nil)
{
scrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view bringSubviewToFront:pageControl];
}
}
#end
I have a table view controller wrapped in a navigation controller. The navigation controller seems to automatically apply the correct content inset to the table view controller when it is presented via presentViewController:animated:completion:. (Can anyone explain to me how this works exactly?)
However, as soon as I wrap the combination in a custom container view controller and present that instead, the topmost part of the table view content is hidden behind the navigation bar. Is there anything I can do in order to preserve the automatic content inset behaviour in this configuration? Do I have to "pass through" something in the container view controller for this to work correctly?
I'd like to avoid having to adjust the content inset manually or via Auto Layout as I want to continue supporting iOS 5.
I've had a similar problem. So on iOS 7, there is a new property on UIViewController called automaticallyAdjustsScrollViewInsets. By default, this is set to YES. When your view hierarchy is in any way more complicated than a scroll/table view inside a navigation or tab bar controller, this property didn't seem to work for me.
What I did was this: I created a base view controller class that all my other view controllers inherit from. That view controller then takes care of explicitly setting the insets:
Header:
#import <UIKit/UIKit.h>
#interface SPWKBaseCollectionViewController : UICollectionViewController
#end
Implementation:
#import "SPWKBaseCollectionViewController.h"
#implementation SPWKBaseCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self updateContentInsetsForInterfaceOrientation:self.interfaceOrientation];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self updateContentInsetsForInterfaceOrientation:toInterfaceOrientation];
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void)updateContentInsetsForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
UIEdgeInsets insets;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
insets = UIEdgeInsetsMake(64, 0, 56, 0);
} else if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
if (UIInterfaceOrientationIsPortrait(orientation)) {
insets = UIEdgeInsetsMake(64, 0, 49, 0);
} else {
insets = UIEdgeInsetsMake(52, 0, 49, 0);
}
}
self.collectionView.contentInset = insets;
self.collectionView.scrollIndicatorInsets = insets;
}
}
#end
This also works for web views:
self.webView.scrollView.contentInset = insets;
self.webView.scrollView.scrollIndicatorInsets = insets;
If there is a more elegant and yet reliable way to do this, please let me know! The hardcoded inset values smell pretty bad, but I don't really see another way when you want to keep iOS 5 compatibility.
FYI in case anyone is having a similar problem: it appears that automaticallyAdjustsScrollViewInsets is only applied if your scrollview (or tableview/collectionview/webview) is the first view in their view controller's hierarchy.
I often add a UIImageView first in my hierarchy in order to have a background image. If you do this, you have to manually set the edge insets of the scrollview in viewDidLayoutSubviews:
- (void) viewDidLayoutSubviews {
CGFloat top = self.topLayoutGuide.length;
CGFloat bottom = self.bottomLayoutGuide.length;
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
}
Thanks to Johannes Fahrenkrug's hint, I figured out the following: automaticallyAdjustsScrollViewInsets indeed only seems to work as advertised when the child view controller's root view is a UIScrollView. For me, this means the following arrangement works:
Content view controller inside Navigation controller inside Custom container view controller.
While this doesn't:
Content view controller inside Custom container view controller inside Navigation controller.
The second option, however, seems more sensible from a logical point of view. The first option probably only works because the custom container view controller is used to affix a view to the bottom of the content. If I wanted to put the view between the navigation bar and the content, it wouldn't work that way.
UINavigationController calls undocumented method _updateScrollViewFromViewController:toViewController in transition between controllers to update content insets.
Here is my solution:
UINavigationController+ContentInset.h
#import <UIKit/UIKit.h>
#interface UINavigationController (ContentInset)
- (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to;
#end
UINavigationController+ContentInset.m
#import "UINavigationController+ContentInset.h"
#interface UINavigationController()
- (void) _updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to;
#end
#implementation UINavigationController (ContentInset)
- (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to {
if ([UINavigationController instancesRespondToSelector:#selector(_updateScrollViewFromViewController:toViewController:)])
[self _updateScrollViewFromViewController:from toViewController:to];
}
#end
Call updateScrollViewFromViewController:toViewController somewhere in your custom container view controller
[self addChildViewController:newContentViewController];
[self transitionFromViewController:previewsContentViewController
toViewController:newContentViewController
duration:0.5f
options:0
animations:^{
[self.navigationController updateScrollViewFromViewController:self toViewController:newContentViewController];
}
completion:^(BOOL finished) {
[newContentViewController didMoveToParentViewController:self];
}];
You don't need to call any undocumented methods. All you need to do is call setNeedsLayout on your UINavigationController. See this other answer: https://stackoverflow.com/a/33344516/5488931
Swift Solution
This solution targets iOS 8 and above in Swift.
My app's root view controller is a navigation controller. Initially this navigation controller has one UIViewController on its stack which I will call the parent view controller.
When the user taps a nav bar button, the parent view controller toggles between two child view controllers on its view using the containment API (toggling between mapped results and listed results). The parent view controller holds references to the two child view controllers. The second child view controller is not created until the first time the user taps the toggle button. The second child view controller is a table view controller and exhibited the issue where it underlaps the navigation bar regardless of how its automaticallyAdjustsScrollViewInsets property was set.
To fix this I call adjustChild:tableView:insetTop (shown below) after the child table view controller is created and in the parent view controller's viewDidLayoutSubviews method.
The child view controller's table view and the parent view controller's topLayoutGuide.length are passed to the adjustChild:tableView:insetTop method like this...
// called right after childViewController is created
adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length)
The adjustChild method...
private func adjustChild(tableView: UITableView, insetTop: CGFloat) {
tableView.contentInset.top = insetTop
tableView.scrollIndicatorInsets.top = insetTop
// make sure the tableview is scrolled at least as far as insetTop
if (tableView.contentOffset.y > -insetTop) {
tableView.contentOffset.y = -insetTop
}
}
The statement tableView.scrollIndicatorInsets.top = insetTop adjusts the scroll indicator on the right side of the table view so it begins just below the navigation bar. This is subtle and is easily overlooked until you become aware of it.
The following is what viewDidLayoutSubviews looks like...
override func viewDidLayoutSubviews() {
if let childViewController = childViewController {
adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length)
}
}
Note that all the code above appears in the parent view controller.
I figured this out with help from Christopher Pickslay's answer to the question "iOS 7 Table view fail to auto adjust content inset".