How to make IBOutlets out of an array of objects? - ios

I want to make an array with a bunch of UIImageViews I have in Interface Builder. Instead of having 20 or 30
IBOutlet UIImageView *img1;
and linking them all that way, and then putting them into an array, is there a way to declare an array of IBOutlet UIImageViews?
Just so I don't have so many declarations in my header file.

It is possible, it’s called outlet collection. This is the way to define an outlet collection:
#property(retain) IBOutletCollection(UIImageView) NSArray *images;
Now you can stick more than one object into the outlet in the Interface Builder, the array will be created for you when the interface is loaded.

I'm a little late here but it may be easier to set the tag property of each ImageView in IB, then access them like [some_superview viewWithTag:tag] rather than keep a separate handle to each one.

Here is more easier way to do it.
Follow these steps to create an array of outlets an connect it with IB Elements:
Create an array of IBOutlets
Add multiple UIElements (Views) in your Storyboard ViewController interface
Select ViewController (In storyboard) and open connection inspector
There is option 'Outlet Collections' in connection inspector (You will see an array of outlets there)
Connect if with your interface elements
-
class ViewController2: UIViewController {
#IBOutlet var collection:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
}
}

Swift 3 and above:
#IBOutlet var stuckLabels: [UIImageView]

There's not, unfortunately, but you can keep all of the declarations on a single line:
IBOutlet UIImageView *img1, *img2, *img3, *img4;
The other option (probably best, since you have so many of these) would be to create them programatically and store them in an array, then add them to the view from your view controller class, using, for each,
[self.view addSubview:img];
Also, keep in mind that if the elements are static (like background elements), and you don't actually need to access them, you don't need to declare outlets for each; you can just add them to the nib file and forget about them.
Same goes for UIButton instances. If you don't need to change anything about the button, you can access it from the method that it calls, like so:
-(IBAction) buttonPressed:(id)sender {
UIButton *button = (UIButton *)sender;
// method guts
// stuff with button -- access tag, disable, etc
}

Related

How to connect 3 UIButton to one outlet

I try to do something like this #IBoutlet var buttons : [UIButton]
but I can't drag button to this outlet
Any idea, how can I achieve that?
It's possible to do that.
Here is how:
Create an array of IBOutlets
Add multiple UIElements (Views) in your Storyboard ViewController interface
Select ViewController (In storyboard) and open connection inspector
There is option 'Outlet Collections' in connection inspector (You will see an array of outlets there)
Connect if with your interface elements
-
class ViewController2: UIViewController {
#IBOutlet var collection:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
}
}
To add a button to an existing array outlet, drag the other way 'round: from the circle in the margin of the view controller to the button. (No need to hold the control key while doing this drag.)

How to create a custom button design with labels inside it in Xcode 6 using Swift

I am learning Swift by creating a small quiz app. I need to create a button with few labels (as shown) for the level. Please guide on how to create such type of button.
The Level number and progress value is dynamic i.e. will be provided programmatically. Is there a way by which I can create a custom button assign it to a class with outlets for labels, so that I can create an object of the class and assign the values of the label and the entire class behaving as a button i.e. when clicked will move to another view controller showing the questions of that level.
You basically answered your own question.
Create a subclass of UIView like so (showing only header file):
#interface MyButton : UIView
#property (nonatomic, weak) IBOutlet UILabel *levelLabel;
#property (nonatomic, weak) IBOutlet UILabel *progressLabel;
#end
Now create a layout file (.xib) with one uiview and uilabels as its children. Set the uiviews class to MyButton and hook up the outlets to the two labels. Remember to set the userInteractionEnabled property of the view to YES.
Use your custom class anywhere in your app by importing "MyButton.h".
EDIT:
In Swift:
class MyButton: UIView {
#IBOutlet weak var lebelLabel: UILabel?
#IBOutlet weak var progressLabel: UILabel?
...
}
Regarding comment:
I created the xib along with labels and their outlets. Can you please tell how to use and initialise MyButton anywhere.
This has been answered on SO countless times before. For one way of doing it, read here

Should i create a view ( consisting UIButton, UILabel etc) in a separate UIView class or inside UIViewController?

I have a UIViewController say viewControllerA which contains some view element like UIButton, UILabel etc. Now my question is should I create those view elements in a separate UIView class and then add in UIViewController, or should I create those view elements directly inside the UIViewController. Accordingly to MVC isn't it appropriate to create view elements inside a separate UIView class and then add this in UIViewController?
The standard place to build the view hierarchy in a UIViewController is in the -viewDidLoad method. That method gets called whenever the UIViewController's view is created. The view controller's view will be loaded from the NIB/Storyboard if applicable; your outlets will be wired up; and then -viewDidLoad is called for you to perform further customization:
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0,0.0,100.0,40.0)];
[self.view addSubView:aLabel];
}
In Cocoa/Cocoa Touch you don't always want to subclass everything the way you would in, say, Java. There are often other preferred means of extending the functionality in built-in classes such as Objective-C categories, delegation, and pre-defined properties.
It's certainly possible to do this sort of thing another way, but this is the most "Cocoa-like" way to do it. Actually, the most "Cocoa-like" way would be to create the view hierarchy in Interface Builder, but if you want to do it programmatically this is the usual way.

Programmatically set an IB object's Class

When using Interface Builder, a lot of the time I'd set the Class property to one of my custom classes. Say I have a UIView in the nib and I set the class to my MyView class.
That's all well and good, even when loading the nib programmatically.
But, is there a way to set the Class of a UI control when programmatically loading a nib? The reason being that I want to sometimes use the same nib created in IB, but have it associated with a different Class.
I don't think there is a proper way to achieve that programmatically.
You could create a naked UIView object in IB and give it a tag or assign it to an IBOutlet for its identification so taht you can access it programmatically in viewDidLoad.
In viewDidLoad you access it and fetch its frame and superview and probably background colour, alpha, hidden status etc, so that you can set all those values in IB. Then remove the UIView from its superview and nil the property (if any) or release the object respectively (depending on ARC or not). Then create the UI Element that you need, assign it to the property (if any) add it to the superview of the former UIView, set its frame and properties accordingly and go from there.
(There are ways in Obj-C to change an object's class after its instanciation, but I would not recommend doing that in this case, especially when your subclasses come with additional properties and ivars.)
Normally you create a UIView object on your UIViewController's class XIB file using Interface Builder and then you use the Custom Class tool in the Identity Inspector to associate the UIView with a class (The class is a UIView class containing the code for drawing on the UIView object.). Then you have to use #property and #systhesize in your UIViewController class to hook the UIView (using the connections inspector) to your class. This method is OK but in certain circumstances it has limitations.
You can get around all of this pragmatically.
Create the UIView class that will be used to draw on a UIView object. In this class you create the context reference (CGContextRef) to give the drawing tools a context (where to draw) for such things as strings, lines, circles, etc. i.e.
CGContextRef gg = UIGraphicsGetCurrentContext();
In the UIViewController class, in the .h file you need to import a reference to your UIView class. Let's call it: DrawOnView
#import "DrawOnView.h"
Then in the brackets encompassing the #interface place this line:
UIView * draw; // draw can be changed to any name that suits your needs
Then in the .m part of the class inside the viewDidLoad method you need to insert this code:
// Make the size and location that suits your needs
// You can change this on the go in your code as needed, such as if the
// device orientation is changed.
draw = [DrawOnView alloc] initWithFrame:CGRectCreate(50, 50, 100, 200)];
// You can change the background color of the view, if you like:
[draw setBackGroundColor:[UIColor greenColor]];
// Now add the view to your primary view
[self.view addSubview:draw];
Now, in other parts of our program, you can call methods you have declared in the DrawOnView class and refresh (which calls the drawRect method, the primary entry point in your UIView class) by using this reference:
[(DrawOnView*) draw setNeedsDisplay];
This is very important. Do not use:
[draw setNeedsDisplay]; // This will not work!
Let's say you have other methods defined in DrawOnView and want to call them.
Here's an example method (in the .h file):
-(BOOL) wasHotSpotHit: (CGPoint) p;
The actual method could look like this (in the .m file):
-(BOOL) washHotSpotHit: (CGPont) p
{
if(CGRectContainsPont(hotspot.frame, p))
{
return true;
}
return false;
}
Use code like this:
if([(DrawOnView*) draw testIfSpotHit:p])
{
// Do something for when user touches hot spot.
}
Try using "object_setClass(id object, Class cls)" method of Objective c runtime. GoodLuck:)

Setup/instantiation of IB subview with custom class

I have a .xib file for my viewController. It contains a TableView and a UIView. The UIView is backed by a custom class - CommentsBarView (trying to position a small bar with a comments field underneath my tableView).
So in my Document Outline list I have:
view
tableView
comments bar view
UITextView
UILabel
Custom class for "comments bar view" is CommentsBarView.
I have outlets connected from within CommentsBarView to the textfield and label.
(and from the ViewController to the tableView).
When I load my with controller with:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
I can access the tableView property and change the appearance of the tableVIew, however, from my commentsBarView initWithCoder I can not set the text value on my textView and label:
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self.commentTextView setText:#"Helo, World!"];
[self.characterCountLabel setText:#"200"];
}
return self;
}
It seems as if these properties are not available at initWithCoder time.
If I manually, from my controllers initWithNibName, accesses self.commentsBar.label.text = 200, there is no problem.
Am I experiencing a timing issue where the views are not ready yet or can I not nest a view inside a viewControllers view and have it backed by a custom UIView subclass?
IB is confusing me a bit.
Thanks for any help given.
When loading from a XIB file, the IBOutlets are not ready in the init methods of the objects being unarchived.
You need to override awakeFromNib in your CommentsBarView to have access to the ready and connected IBOutlets.
Once you get used to IB it becomes better. Since Cocoa is a MVC (Model-View-Controller) you should probably not create a UIView subclass and set your UIView to it. You should probably put the UIView back to just a UIView. I generally subclass a UIView if I need to have a customized look. For example; a good time to subclass a UIView is if you want it to have a gradient. Then you can reuse your subclass for any UIView you wish to show the gradient.
In your case you are trying to "control" the UITextView and UILabel. You can instead wire-up outlets of your UITextView and UILabel directly to your UIViewController (File Owner). That is the "controller" of the MVC in this case. Think of your UIView as a container that is simply holding the two controls for this example. Now you can use the viewDidLoad method or some other method of you UIViewController to set the values of you UITextView and UILabel. It is generally the UIVeiwController that interprets the data from the Model in Cocoa and places the data where it needs to be. It is not a rock-solid rule, but a good one.

Resources