Cocoa/Objective-C noob here.
I'm working on a simple app to learn some more about iOS development, and am struggling to see how my subclass of UITextView has it's viewDidLoad method called.
I am subclassing UITextView to CMTextView.
Using storyboard, I have a CMTextView in the window.
In CMTextView.m, I have the following:
#import "CMTextView.h"
#interface UITextView ()
- (id)styleString;
#end
#implementation CMTextView
- (id)styleString {
return [[super styleString] stringByAppendingString:#"; line-height: 1.1em"];
}
- (void)viewDidLoad {
NSLog(#"LOADED!"); // not doing anything...
}
I'm not doing anything fancy to add this as a subview of my window, but I thought the storyboard did that for me?
Does anyone have any advice?
The above answers are correct if you manually initialize the UITextView. If loading them from a nib, you need to override the -awakeFromNib method.
-(void)awakeFromNib
{
[super awakeFromNib]; // Don't forget to call super
//Do more intitialization here
}
If you want to handle the resizing also override the -layoutSubviews method
viewDidLoad is a method in UIViewController not UIView/UITextView
if you want to do any initialize then put it in initWithFrame:
viewDidLoad is a UIViewController method (see here).
UIKit ensures that viewDidLoad is called at appropriate times when you instantiate a view controller's view, but it has no role to play in a UIView like UITextView.
Usually, you prepare your view at initWithFrame time.
A view can never have the method viewDidLoad.
Some methods are tethered only to the viewcontroller and viewDidLoad is one of them.
The method constructors for views would be
-(id)init;
-(id)initWithFrame;
I'm looking to use the UIPageViewController with Xcode's storyboard. The only example I could find was this tutorial on how to implement UIPageViewcontroller using separate xib files.
However in this tutorial the UIPageViewController is instantiated programmatically within a class that extends UIViewController. This makes it complicated to translate in to how the storyboard interprets the UIPageViewcontroller (which is as an instance on its own).
Any help on how to create a functioning UIPageViewController with Xcode's storyboard will be much appreciated.
UPDATE: I managed to resolve this issue by making a new project in Xcode using the default pagecontroller template. It used the storyboard and was easy to follow.
UPDATE: I managed to resolve this issue by making a new project in xcode using the default pagecontroller template. It used the storyboard and was easy to follow.
Setting up the Xcode Project to be a PageViewController is one way of accomplishing this, however if you'd like to include a PageViewController within an already existing storyboard you can do this as well.
If you drag and drop a PageViewController Scene onto your storyboard and wire up a segue, you can push to that PageViewController. However there appears to be some bugs in the storyboard for setting up the PageViewController properly, for instance you cannot wire up the delegate and datasource.
An easy way around this is to simply wire the delegate/datasource up in your init method:
- (instancetype) initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
self.delegate = self;
self.dataSource = self;
}
return self;
}
This will cause your delegate and datasource methods to be called properly, assuming of course you want the datasource and delegates to be the PageViewController. Once this is set up you will need to ensure that there is a view controller when the view is loaded. You can do this with the setViewControllers method on PageViewController class in your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setViewControllers:#[sweetViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
NSLog(#"Hooray I set my initial viewcontroller for my page view controller");
}];
}
When the PageViewController is created, it will start with your sweetViewController, and then begin calling your datasource and delegate methods as needed.
There's an easy way to do this with Xcode 7. Have the ViewController that will be your datasource and delegate in one Storyboard Scene, with your UIPageViewController embedded into a ContainerView. Then capture the UIPageViewController in your ViewController's prepareForSegue Method.
e.g. in Swift:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier where identifier == "yourPageVCIdentifier"{
if let pageController = segue.destinationViewController as? UIPageViewController {
pageController.dataSource = self
pageController.delegate = self
self.pageVC = pageController
}
}
}
I have difficulty adding a subview (UIView) from within the viewDidLoad method of a UITableViewController
This works:
[self.view addSubview:self.progView];
But you can see the table cell lines bleed through the UIView progView.
I've tried this approach:
[self.view.superview insertSubview:self.progView aboveSubview:self.view];
Which is an attempt to add the progView, UIView to the superview, above the current view. When I try this, the UIView never appears.
-- UPDATE --
Following is the latest attempt:
UIView *myProgView = (UIView *)self.progView; //progView is a method that returns a UIView
[self.tableView insertSubview:myProgView aboveSubview:self.tableView];
[self.tableView bringSubviewToFront:myProgView];
Result is the same as [self.view addSubview:self.progView]; The UIView appears but seemingly behind the Table.
I tried the approach above, but did not get it to work. I also found it to require too much configuration and code, since it requires setting up the table view from scratch (something that is easily done from within the storyboard).
Instead, I added the view that I wanted to add above my UITableView into the UITableViewController's UINavigationController's view, as such:
[self.navigationController.view addSubview:<view to add above the table view>];
This approach requires that you have embedded the UITableViewController in a UINavigationController, but even if you do not want a navigation controller, you can still use this approach and just hide the navigation bar.
So 7 years have passed since my original answer, and I happen to stumble upon this problem again. Let's solve this properly once and for all:
In viewDidLoad, add your subview to the (table) view.
Pin the constraints relative to the safe area layout guide. This stops it from scrolling with the table contents (as pointed out in cornr's answer).
In viewDidLayoutSubviews, bring the subview to the front. This ensures it doesn't get lost behind the table separators.
Swift:
override func viewDidLoad() {
super.viewDidLoad()
// 1.
view.addSubview(mySubview)
// 2. For example:
mySubview.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mySubview.widthAnchor.constraint(equalToConstant: 100),
mySubview.heightAnchor.constraint(equalToConstant: 100),
mySubview.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
mySubview.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 3.
view.bringSubviewToFront(mySubview)
}
Objective-C:
- (void)viewDidLoad
{
[super viewDidLoad];
// 1.
[self.view addSubview:self.mySubview];
// 2.
self.mySubview.translatesAutoresizingMaskIntoConstraints = false;
[NSLayoutConstraint activateConstraints:#[
[self.mySubview.widthAnchor constraintEqualToConstant:100],
[self.mySubview.heightAnchor constraintEqualToConstant:100],
[self.mySubview.centerXAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.centerXAnchor],
[self.mySubview.centerYAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.centerYAnchor]
]];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// 3.
[self.view bringSubviewToFront:self.mySubview];
}
Phew, glad that's done! Seeing how much saner this answer is, I'll omit my original answer.
Fun fact: 7 years on and I'm still an iOS developer.
Ive been able to add a subview on top of a uitableviewcontroller by using uiviewcontroller containment.
UITableViewController is actually very handy when it comes to static cells and this is probably the only time where the common answer "just use uitableview" may actually not viable.
So this is how I do it.
give your UITableViewController a StoryBoard identifier i.e. MyStaticTableView
create a brand new UIViewController subclass and call it UITableViewControllerContainer
place this controller in place of your UITableViewController inside your storyboard
add a subview to the new controller and link it to an outlet called like "view_container"
on you UITableViewControllerContainer viewDidLoad method
add code like:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UITableViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"MyStaticTableView"];
[self addChildViewController:vc];
[self.view_container addSubview:vc.view];
}
Problems you may have:
if you have extra top space then be sure to add the flag "wants fullscreen" to your UITableViewController
if it doesn't resize properly on your UITableViewControllerContainer
add code:
- (void)viewWillAppear:(BOOL)animated
{
[[self.view_container.subviews lastObject] setFrame:self.view.frame];
}
at this point from your UITableViewController you can access you container view directly with
self.view.superview.superview
and whatever you add to it will be show on top your table view controller
Swift 2018
Here is my way with storyboard:
1) Add a view in storyboard.
2) Link it with UITableViewController class:
#IBOutlet weak var copyrightLabel: UILabel!
3) Add it in code
self.navigationController?.view.addSubview(copyrightView)
copyrightView.frame = CGRect(x: 0,
y: self.view.bounds.size.height - copyrightView.bounds.size.height,
width: self.view.bounds.size.width,
height: copyrightView.bounds.size.height)
4) Voilla!
The view will not scroll with the table view. It can be easy designable from the storyboard.
NOTE:
This solution adds subview to the navigation controller and if you are going to another screen from here further down the nav, you will find this subview to persist, remove it using copyrightView.removeFromSuperView on viewDidDisappear while performing segue.
You can increase the zPosition of the layer of your view.
This will make it display above the other views (which have a zPosition equal to 0, by default)
self.progView.layer.zPosition++;
[self.view addSubview:self.progView];
As UITableViewController is a subclass of UIViewController, you need to add your desired view to its superview.
Swift:
self.view.superview?.addSubview(viewTobeAdded)
Objective C:
[self.view.superview addSubview: viewTobeAdded];
The problem is that the view property of UITableViewController is identical to the tableView property. What this means is that the root view is always a table view controller, and anything added as a subview will be subject to the table view functionality. This has other undesirable side effects, like your subviews scrolling when you may not want them to.
There are a couple options here. You could override loadView and install your own view and table view:
// note: untested
- (void)loadView {
self.view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view.backgroundColor = [UIColor whiteColor];
UITableView *tblView = [[UITableView alloc]
initWithFrame:CGRectZero
style:UITableViewStylePlain
];
tblView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight
;
self.tableView = tblView;
[self.view addSubview:tblView];
[tblView release];
}
And then when you need to add a subview, add it below or above self.tableView as appropriate.
Another option is just to create a UIViewController subclass that does what you need. UITableViewController honestly doesn't add that much, and the little functionality it does implement is easily replicated. There are articles like Recreating UITableViewController to increase code reuse that explain how to do this pretty easily.
I had similar problem and got it solved using below code :
[self.navigationController.view insertSubview:<subview>
belowSubview:self.navigationController.navigationBar];
This inserts view in correct place using controllers present in Stack.
The Apple example "iPhoneCoreDataRecipes" is using a NIB to load a header onto a UITableView.
See here:
I added a image above the the table. I used this code:
self.tableView.tableHeaderView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"header.png"]];
Solution for swift (equivalent to Daniel Saidi):
If your controller is a UITableViewController in a Storyboard or XIB and you wish to reassign self.view to a standard UIView while preserving your existing table view:
#IBOutlet var tableViewReference: UITableView!
var viewReference: UIView!
Then in your implementation file:
Add these instance variables to your table view controller file:
override var tableView: UITableView! {
get { return tableViewReference }
set { super.tableView = newValue }
}
override var view: UIView! {
get { return viewReference }
set { super.view = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
self.edgesForExtendedLayout = UIRectEdge.None
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
//rewiring views due to add tableView as subview to superview
viewReference = UIView.init(frame: tableViewReference.frame)
viewReference.backgroundColor = tableViewReference.backgroundColor
viewReference.autoresizingMask = tableViewReference.autoresizingMask
viewReference.addSubview(tableViewReference)
}
In your Storyboard or XIB file: Connect the tableView in the UITableViewController to the tableViewReference variable.
Then you will be able to add child views as follows:
self.view.addSubView(someView)
Update for iOS 11:
It is now possible to add an Subview to UITableView when using AutoLayout constraints to the safe area. These Views will not scroll along the TableView.
This example places a view below the NavigationBar on top of the UITableView of a UITableViewController
[self.tableView addSubview:self.topBarView];
[NSLayoutConstraint activateConstraints:#[
[self.topBarView.topAnchor constraintEqualToAnchor:self.tableView.safeAreaLayoutGuide.topAnchor],
[self.topBarView.leadingAnchor constraintEqualToAnchor:self.tableView.safeAreaLayoutGuide.leadingAnchor],
[self.topBarView.trailingAnchor constraintEqualToAnchor:self.tableView.safeAreaLayoutGuide.trailingAnchor],
[self.topBarView.heightAnchor constraintEqualToConstant:40.0]
]];
try something like this:
[self.tableView addSubview:overlayView];
overlayView.layer.zPosition = self.tableView.backgroundView.layer.zPosition + 1;
You may simply put the following code in viewDidAppear:
[self.tableView.superview addSubview:<your header view>];
try: [self.view bringSubviewToFront:self.progView];
Or you can try to add self.progView to your table's view.
To keep UIView above table view in UITableViewController I'm using one(or more) of delegate methods (UITableViewDelegate).
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addSubview(headerView)
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
tableView.addSubview(overlayView) // adds view always on top
}
// if using footers in table view
tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { ... }
As views can only have one superview that seams too be good solution, correct me if I'm wrong. Still getting 60fps so it's fine for me.
Swift 4
This is the most simplified version of a number of answers here where we are recomposing the view hierarchy. This approach does not require additional outlets for storyboards / nibs and will also work with programmatically constructed instances.
class MyTableViewController: UITableViewController {
var strongTableView: UITableView?
override var tableView: UITableView! {
get {
return strongTableView ?? super.tableView
}
set {
strongTableView = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
// theoretically we could use self.tableView = self.tableView but compiler will not let us assign a property to itself
self.tableView = self.view as? UITableView
self.view = UIView(frame: self.tableView!.frame)
self.view.addSubview(tableView)
}
}
To add a customView above the current UITableViewController, it must be a nice way to use 'self.navigationController.view addSubview:customView' like Daniel commented.
However, in case of implementing customView that serves as navigationBar, Daniel's way can cause unexpected result to default or custom navigationBar on other navigationViewControllers that is in front and back of the UITableViewController.
The best simple way is just converting UITableViewController into UIViewController which has no limit on layout it's subviews. But, if you're struggling with massive, long legacy UITableViewController code, the story is totally different. We don't have any sec for converting.
In this case, you can simply highjack tableView of UITableViewController and solve this whole problem.
The most important thing we should know is UITableViewController's 'self.view.superview' is nil, and 'self.view' is UITableView itself.
First, highjack the UITableVIew.
UITableView *tableView = self.tableView;
Then, replace 'self.view'(which is now UITableView) with a new UIView so that we can layout customViews with no-limitation.
UIView *newView = UIView.new;
newView.frame = tableView.frame;
self.view = newView;
Then, put UITableView we highjacked before on the new self.view.
[newView addSubview:tableView];
tableView.translatesAutoresizingMaskIntoConstraints = NO;
[tableView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
Now, we can do whatever we want on this brand new fancy 'self.view' on UITableViewController.
Bring a custom View, and just add as subView.
UIView *myNaviBar = UIView.new;
[myNaviBar setBackgroundColor:UIColor.cyanColor];
[self.view addSubview:myNaviBar];
myNaviBar.translatesAutoresizingMaskIntoConstraints = NO;
[myNaviBar.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[myNaviBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[myNaviBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[myNaviBar.heightAnchor constraintEqualToConstant:90].active = YES;
gif
There may be reasons not to do this, but this works for me so far. If you use an ap
Inside viewDidLayoutSubviews you can run this, but make sure to only run it once obviously
self.searchTableView = [[UITableView alloc] initWithFrame:self.tableView.frame style:UITableViewStylePlain];
self.searchTableView.backgroundColor = [UIColor purpleColor];
[self.view.superview addSubview:self.searchTableView];
I had a similar problem where I wanted to add a loading indicator on top of my UITableViewController. To solve this, I added my UIView as a subview of the window. That solved the problem. This is how I did it.
-(void)viewDidLoad{
[super viewDidLoad];
//get the app delegate
XYAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
//define the position of the rect based on the screen bounds
CGRect loadingViewRect = CGRectMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2, 50, 50);
//create the custom view. The custom view is a property of the VIewController
self.loadingView = [[XYLoadingView alloc] initWithFrame:loadingViewRect];
//use the delegate's window object to add the custom view on top of the view controller
[delegate.window addSubview: loadingView];
}
This worked for me:
self.view.superview?.addSubview(yourCustomView)
self.view.bringSubviewToFront(yourCustomView)
I need to create my own UIView class and it is not something I have had to do. I created the class, then laid out the small view in IB (it's just a few labels that i will later need to add data to ). but now im stuck on how to actually put an instance of it in my main view. Can someone point me in the direction of a good tutorial? The closest thing I have done to this is creating a custom tableViewCell.
DataTagViewController.m:
- (id)initWithNibNamed:(NSString *)DataTagViewController bundle:bundle {
if ((self = [super initWithNibName:DataTagViewController bundle: bundle])) {
// Custom initialization
}
return self;
}
MapView.m:
DataTagViewController *dataTag = [[DataTagViewController alloc] initWithNibNamed:#"DataTagViewController" bundle:nil];
[theMap addSubView: dataTag.view]; <<< this line causes the crash (theMap is a UIView)
I now get this runtime error when adding the subview:-[UIView addSubView:]: unrecognized selector sent to instance 0x470f070'
2010-06-06 21:22:08.931
UIViewController is not a view, but controls a view. If your DataTagViewController class extends UIViewController, then you'll want to add it's view, not the class itself:
[theMap addSubView:dataTag.view];
Also, do you have a DataTagViewController.xib file created that has your view in it? If you don't, you'll need to create one and use the UIViewController's initWithNibName:bundle method. Otherwise, you'll have to implement the loadView method instead to provide your own view via code.
Edit
Your init function is using the name of your class as a variable. That probably won't work. Use the default sigature:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
}
}
If you aren't doing anything beyond the init function, you don't need to implement this method. Your alloc/init statement is enough.
For a good tutorial, read the View Controller Programming guide in the docs.
What is the parent class of DataTagViewController? You say you need to create "my own UIView class", but your example suggests that you actually want to create UIViewController subclass. initWithNibNamed: is UIViewController method. If your parent is UIView, then "unrecognized selector" makes sense.
I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.