Create multiple UIViews dynamically - ios

I want to create a bar chart using UIView animations. The bar chart will be dynamically created, using data retrieved from my persistent store.
To teach myself how to use UIView animations, I followed this Ray Wenderlich tutorial then built a modified app based on it. As a result, I feel comfortable with the animation aspects--at least as long as it involves discrete (Storyboard-created) views specified by hard-coded data.
However, in the full implementation, the bar chart will have multiple columns, the number, heights, widths, etc. of which will be specified proportionally by live data.
At this point, I've created a UIView in the storyboard, linked to a custom UIView subclass called RiserBar, thinking to create instances of the views (RiserBar *) on the fly. However--and I feel kinda dumb admitting it--I realize I don't really know how to implement this idea. For example, if I'm iterating through an array containing the specifications for several bars, what would that look like? Would each bar have a name? How would those names be generated?
Here's the code I'm thinking about. aBAr is an instance variable. The frame specification, as well as specArray are just dummies, as the exact specification mechanism isn't clear just yet, but the idea is to create as many RiserBar objects as there are entries in specArray:
-(void) makeBarChart
{
RiserBar *aBar;
for (int i=0; i<specArray.count; i++)
{
aBar = [[RiserBar alloc]initWithFrame:CGRectZero];
aBar.backgroundColor = [UIColor redColor];
[self.view addSubview:aBar];
[UIView animateWithDuration:1
delay:.2
options: UIViewAnimationCurveEaseOut
animations:^
{
aBar.frame = CGRectMake(50, self.view.frame.origin.y, startingSpec.width, startingSpec.height);
aBar.frame = CGRectMake(50, self.view.frame.origin.y, endSpec.width, endSpec.height);
}
completion:^(BOOL finished)
{
NSLog(#"Done!");
}];
}
}
Please help me straighten out my thinking about this!
Thanks!

Related

Dynamic View Controller

I just need to find out several things about iOS views and layout. I have just ended application where I had to create a controller that show test. It was easy except one thing. There was two types of question A and B. A questions have 4 variants of answers, B questions don't. So I had to generate all my layout in code depends on question type. I've done it by creating method update where i rebuild my views.
- (void)update {
for (UIView *v in self.view.subviews) {
[v removeFromSuperview];
}
//label initialization with frame calculation
UILabel *questionLabel = [[UILabel alloc]initWithFrame:CGRectMake(PADDING, PADDING, size.width - 4 * PADDING, 0)];
[questionLabel sizeToFit];
[self.view addSubview:questionContainerView];
//If type A
if ([self.question.questionType isEqualToString:#"A"]) {
for (Answer *a in self.question.answers) {
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(PADDING, offset, size.width - 2 * PADDING, 30 + PADDING)];
//customization
[self.view addSubview:button];
offset += button.frame.size.height + MARGIN_BOTTOM;
}
//if B type
} else {
UITextField *answerField = [[UITextField alloc] initWithFrame:CGRectMake(PADDING, PADDING, view.frame.size.width - 2*PADDING, view.frame.size.height - 2 * PADDING)];
[view addSubview:answerField];
offset += view.frame.size.height + MARGIN_BOTTOM;
}
UIView *lastView = [[self.view subviews]lastObject];
self.scrollView.contentSize = CGSizeMake(size.width, lastView.frame.origin.y + lastView.frame.size.height + 10);
}
And after all I think it's can't be so sophisticated. Please help to find out how should I do it properly, because I am sure my method is not optimal. Maybe create custom layout. I mean how to avoid setting frame and simply add views which take right position.
This is extremely vague and I am assuming you are against using any GUI tools? Regardless, rather than creating each view after each update why not just create both views then hide or show them depending on the question?
You can do this simply using bringSubviewToFront or sendSubviewToBack or simply setHidden. That way you can create your views and simply show or hide them without destroying them and recreating everything although something this simple can easily be done that way.
My advice? Create these subviews using a storyboard or Xib then simply hide or show the view of your choice. That way you can easily layout the views however you like without touching any code.
Now, if the questions all change constantly than this is the EXACT place you would want to use a UITableViewController as this is what it's designed for. You can create a few custom UITableViewCells for your different question scenarios then reuse them depending on the type of questions. Then you simply update your dataSource for the tableView and all of your new questions appear how you like.

How to add multiple subviews to my iOS view in one line of code? (via "monkeypatching" an `addSubviews` instance method to UIView perhaps?)

lets just say I had just one UILabel subview element & one UITextView subview element inside of a given ViewController.m's viewDidLoad method like so:
UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake(20, 140, 280, 40)];
name.text = #"Name: Eric";
UITextView *bio = [[UITextView alloc] initWithFrame:CGRectMake(20, 190, 280, 80)];
bio.text = "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature fro...";
In a normal fashion I could add them one at a time:
[self.view addSubview:name];
[self.view addSubview:bio];
But if there were several dozen or perhaps hundreds of UI components, adding each of them one at time would add dozens to hundreds of lines of code to my project.
Coming from a ruby background, I believe brevity is very important. Is there any way to pass in my UI elements as an array like so?:
[self.view addSubviews:#[name, bio]];
It might be a little early for me to start patching iOS's libraries at boot time but that would definitely be an acceptable solution to me.
Coming from a ruby/rails template of understanding, I'm wondering, is there something equivalent to Rail's /initializers folder in iOS applications? FYI non/pre-rails folks, the /initializers folder in rails is scanned by rails at the beginning of any rails session, and all code inside its .rb files is executed before anything else in rails is loaded aside from the dependencies libraries which are loaded just before that. Thus the ActiveRecord gem (library) for example could then be reinstantiated in an initializer and be amended to at that point. I know Objective C is immutable in a lot of cases though and don't know if that would be a problem.
Existing Cocoa library methods are of course preferred.
In ruby I'd probably patch the method to UIViewController maybe a little something like this:
class UIViewController
def addSubviews(subviews)
subviews.each {|obj| self.view.addSubview(obj) }
end
end
I'd go for a category on UIView.
File --> New --> File --> Objective-C category. Name it something like EasyAdditionOfSubviews, make it a category on UIView.
UIView+EasyAdditionOfSubviews.h
#import <UIKit/UIKit.h>
#interface UIView (EasyAdditionOfSubviews)
- (void)addSubviews:(NSArray *)views;
#end
UIView+EasyAdditionOfSubviews.m
#import "UIView+EasyAdditionOfSubviews.h"
#implementation UIView (EasyAdditionOfSubviews)
- (void)addSubviews:(NSArray *)views
{
for (UIView *view in views) {
if ([view isKindOfClass:[UIView class]]) {
[self addSubview:view];
}
}
}
#end
This way, you just #import UIView+EasyAdditionOfSubviews.h anywhere you want to be able to add an array of views at a time.
And you're better off making sure that what you're passing to addSubview: is indeed a UIView, otherwise you risk a crash (for example, try passing it an NSArray).
But to address your wider concern:
But if there were several dozen or perhaps hundreds of UI components,
adding each of them one at time would add dozens to hundreds of lines
of code to my project.
If you're building a UI programmatically, you want to be exactly sure what, how, and when subviews are added to a view; it's perfectly fine to be explicit when adding a subview to a view.
As a matter of fact, your workaround to not explicitly adding the subview to its superview is... to explicitly add it to an array, which you then add to the superview. Not good for brevity!
Assuming subViews is an array, you could do:
[subViews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[superview addSubview:obj];
}];
As #pranav mentions, an alternative is to use the for..in syntax:
for (UIView* view in subViews) {
[superview addSubview:view];
}

iOS alert banner/label?

I apologize if this question is very basic. I have been googling around but can't seem to find the api/reference for a drop down alert banner/label (I do not know the proper term for this), therefore I am posting here.
This: The label/banner which has "Please enter valid email address" in it.
So here a my questions:
What is the proper term for this (alert banner? notification? label?)
I am trying accomplish similar functionality to what is shown in the image, so basically if any field is invalid, the "label/banner" expands from underneath the navigation bar with the message in it:
If this is just a UILabel, what is the simplest way of adding the expand animation?
If it is something built in, since I have seen bunch of apps do it for alerting, please let me know what its called.
Have a look here, I'm sure you will be able to find something to suite your needs.
The basic idea is that its simply a UIView that you animate down from the top of the screen (at the very basic). You can get a lot fancier by adding gradients, touch recognizers to dismiss it, etc. But pretty much to get the base line functionality you would just do something like this:
//Create a view to hold the label and add images or whatever, place it off screen at -100
UIView *alertview = [[UIView alloc] initWithFrame:CGRectMake(0, -100, CGRectGetWidth(self.view.bounds), 100)];
//Create a label to display the message and add it to the alertView
UILabel *theMessage = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(alertview.bounds), CGRectGetHeight(alertview.bounds))];
theMessage.text = #"I'm an alert";
[alertview addSubview:theMessage];
//Add the alertView to your view
[self.view addSubview:alertview];
//Create the ending frame or where you want it to end up on screen, in this case 0 y origin
CGRect newFrm = alertview.frame;
newFrm.origin.y = 0;
//Animate it in
[UIView animateWithDuration:2.0f animations:^{
alertview.frame = newFrm;
}];
Check out my project - it might be just the thing you're looking for.
https://github.com/alobi/ALAlertBanner
For easier control over animating the alert, you can embed your custom view in a UIStackView and simply show/hide it in an animation block. That way will significantly reduce the amount of code needed to animate the visibility of the alert.

Managed array of UIView using some method

Hello I am noob in Xcode and I want to ask
I have array of UIView's with one image
NSInteger i;
for (i=0; i<CARD_AMOUNT; i++) {
[cardArray addObject:[UIImage imageNamed:#"Back.png"]];
}
and I want to draw it and then to do different things on each (move...etc)
or I must create 10 variables of UIImageView?
Could use a bit more explanation of the app you're trying to build, but essentially if you're wanting to display many different cards on one view (let's say, a game of solitaire) - you'll want to create many UIImageViews. Well, from an pure design perspective, you'd probably want a different class called CardView which knew how to render itself
So, if you wanted many different views all with one image, something like this:
for(int a=0; a<CARD_AMOUNT; a++) {
UIImageView *view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Back.png"]];
[cardArray addObject:view];
// and maybe you'd want to set the frame locations, or add them to your current view as subviews, or ..
}
To address the comments, your custom CardView might have something like this (there's ways to optimize, but the general concept isn't too bad):
- (void)setRank: (int)rank andSuit:(int)suit {
self.image = [UIImage imageNamed:[NSString stringWithFormat:#"card-%d-%d.png",rank,suit];
self.rank = rank; self.suit = suit;
}
or, better yet (since a card's rank won't change) - some initWithRank: andSuit: method.. dunno.
There's probably 10 other ways to design this.

Animated Resize of UIToolbar Causes Background to be Clipped on iOS <5.1

I have implemented a custom split view controller which — in principle — works quite well.
There is, however one aspect that does not work was expected and that is the resize-animation of the toolbar on iOS prior to version 5.1 — if present:
After subclassing UIToolbar to override its layoutSubviews method, animating changes to the width of my main-content area causes the toolbar-items to move as expected. The background of the toolbar — however — does not animate as expected.
Instead, its width changes to the new value immediately, causing the background to be shown while increasing the width.
Here are what I deem the relevant parts of the code I use — all pretty standard stuff, as little magic/hackery as possible:
// From the implementation of my Split Layout View Class:
- (void)setAuxiliaryViewHidden:(BOOL)hide animated:(BOOL)animated completion:(void (^)(BOOL isFinished))completion
{
auxiliaryViewHidden_ = hide;
if (!animated)
{
[self layoutSubviews];
if (completion)
completion(YES);
return;
}
// I've tried it with and without UIViewAnimationOptionsLayoutSubviews -- didn't change anything...
UIViewAnimationOptions easedRelayoutStartingFromCurrentState = UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:M_1_PI delay:0.0 options:easedRelayoutStartingFromCurrentState animations:^{
[self layoutSubviews];
} completion:completion];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// tedious layout work to calculate the frames for the main- and auxiliary-content views
self.mainContentView.frame = mainContentFrame; // <= This currently has the toolbar, but...
self.auxiliaryContentView.frame = auxiliaryContentFrame; // ...this one could contain one, as well.
}
// The complete implementation of my UIToolbar class:
#implementation AnimatableToolbar
static CGFloat sThresholdSelectorMargin = 30.;
- (void)layoutSubviews
{
[super layoutSubviews];
// walk the subviews looking for the views that represent toolbar items
for (UIView *subview in self.subviews)
{
NSString *className = NSStringFromClass([subview class]);
if (![className hasPrefix:#"UIToolbar"]) // not a toolbar item view
continue;
if (![subview isKindOfClass:[UIControl class]]) // some other private class we don't want to f**k around with…
continue;
CGRect frame = [subview frame];
BOOL isLeftmostItem = frame.origin.x <= sThresholdSelectorMargin;
if (isLeftmostItem)
{
subview.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
continue;
}
BOOL isRightmostItem = (CGRectGetMaxX(self.bounds) - CGRectGetMaxX(frame)) <= sThresholdSelectorMargin;
if (!isRightmostItem)
{
subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
continue;
}
subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
}
}
#end
I’ve set the class of the toolbar in InterfaceBuilder and I know for a fact, that this code gets called and, like I said, on iOS 5.1 everything works just fine.
I have to support iOS starting version 4.2, though…
Any help/hints as to what I’m missing are greatly appreciated.
As far as I can see, your approach can only work on iOS SDK > 5. Indeed, iOS SDK 5 introduced the possibility of manipulating the UIToolbar background in an explicit way (see setBackgroundImage:forToolbarPosition:barMetrics and relative getter method).
In iOS SDK 4, an UIToolbar object has no _UIToolbarBackground subview, so you cannot move it around in your layoutSubviews implementation. To verify this, add a trace like this:
for (UIView *subview in self.subviews)
{
NSLog(#"FOUND SUBVIEW: %#", [subview description]);
run the code on both iOS 4 and 5 and you will see what I mean.
All in all, the solution to your problem lays in handling the background in two different ways under iOS 4 and iOS 5. Specifically, on iOS 4 you might give the following approach a try:
add a subview to your custom UIToolbar that acts as a background view:
[toolbar insertSubview:backgroundView atIndex:0];
set:
toolbar.backgroundColor = [UIColor clearColor];
so that the UIToolbar background color does not interfere;
in your layoutSubviews method animate around this background subview together with the others, like you are doing;
Of course, nothing prevents you from using this same background subview also for iOS 5, only thing you should beware is that at step 1, the subview should be inserted at index 1 (i.e, on top of the existing background).
Hope that this helps.
Since I think this is going to be useful for someone else, I’ll just drop my solution here for reference:
Per sergio’s suggestion, I inserted an additional UIImageView into the view hierarchy. But since I wanted this to work with the default toolbar styling, I needed to jump trough a few hoops:
The image needed to be dynamically generated whenever the tintColor changed.
On iOS 5.0.x the toolbar background is an additional view.
To resolve this I ended up…
Implementing +load to set a static BOOL on whether I need to do anything. (Parses -[UIDevice systemVersion] for version prior to 5.1).
Adding a (lazily loaded) property for the image view stretchableBackground. The view will be nilif my static flag is NO. Otherwise the view will be created having twice the width of [UIScreen mainScreen], offset to the left by half that width and resizable in height and right margin and inserted into the toolbar at index 0.
Overriding setTintColor:. Whenever this happens, I call through to super and __updateBackground.
Implemented a method __updateBackground that:
When the toolbar responds to backgroundImageForToolbarPosition:barMetrics: get the first subview that is not our stretchableBackground. Use the contents property of that view’s layer to populate the stretchableBackground’s image property and return.
If the toolbar doesn’t respond to that selector,
use CGBitmapContextCreate() to obtain a 32bit RGBA CGContextRef that is one pixel wide and as high as the toolbar multiplied by the screen’s scale. (Use kCGImageAlphaPremultipliedLast to work with the device RGB color space…)
Translate the CTM by that height and scale it by scale/-scale to transition from UIKit to CG-Coordinates and draw the view’s layer into that context. (If you fail to do this, your image will always be transparent blank…)
Create a UIImage from that context and set it as the stretchableBackground’s image.
Notice that this fix for iOS 5.0.x will not work as expected when using different background images for portrait and landscape or images that do not scale — although that can be tweaked by configuring the image view differently…

Resources