I need to generate a grid of buttons for an iOS app. Each button needs two parameters: the number of the column and the number of the row. A button will also have two states, activated and deactivated.
When the app is loaded I want there to be like 21 rows and 16 columns. And somewhere on the screen there will also be a button that says "add columns" and this would add 4 extra columns every time it's clicked.
Any suggestions how I should do this? I could start by adding the first 21*16 buttons with the IB but will that allow me to extend it with extra columns later on, and how?
Edit: the bounty was only started to reward mbm30075, no new answers necessary
I believe this is a more complete solution. Here's the .h/.m pair I wrote up for testing:
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (assign, nonatomic) int numColumns;
#property (assign, nonatomic) int numRows;
#property (strong, nonatomic) NSMutableArray *buttonsArray;
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (strong, nonatomic) IBOutlet UIButton *addButtons;
#property (strong, nonatomic) IBOutlet UIView *buttonsView;
- (IBAction)addFourMoreColumns;
#end
ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize addButtons;
#synthesize buttonsArray;
#synthesize buttonsView;
#synthesize numColumns;
#synthesize numRows;
#synthesize scrollView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setButtonsArray:[NSMutableArray array]];
[self setNumColumns:16];
[self setNumRows:21];
[self layoutButtons];
}
- (void)layoutButtons {
static int width = 100;
static int height = 37;
static int buffer = 8; // space between buttons (horiz. & vert.)
static int margin = 20;
CGPoint topLeft = CGPointMake(margin,margin); // standard "top left" in iOS
// Since you appear to be wanting to modify the number of columns,
// I'm going to make the multi-dimension array an array of columns,
// with each column containing an array of buttons (representing the rows)
// Iterate through how many columns SHOULD exist ([self numColumns])
for (int i = 0; i < [self numColumns]; i = i + 1) {
// Check if this "column" exists (does this index exist in [self buttonsArray]?)
if (i >= [[self buttonsArray] count]) {
// It doesn't exist, so we need to add a blank array
[[self buttonsArray] addObject:[NSMutableArray array]];
}
NSMutableArray *column = [[self buttonsArray] objectAtIndex:i];
// Now, we iterate through how many rows/buttons SHOULD exist ([self numRows])
for (int j = 0; j < [self numRows]; j = j + 1) {
// Check if this "row"/"cell"/"button" exists
if (j >= [column count]) {
// It doesn't exist, so we need to add a new button AND PLACE IT!
// Of course, you need to make your button type correctly
// This is just standard button code...
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(topLeft.x,topLeft.y,width,height)];
// Do whatever else you need to do with the button...
// Set title...
[btn setTitle:[NSString stringWithFormat:#"(%d,%d)", i + 1, j + 1] forState:UIControlStateNormal];
// Add target actions...
[btn addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
// Add the button to the view
[[self buttonsView] addSubview:btn];
// Add the button to the array
[column addObject:btn];
}
// Increment topLeft to the next "row" for correct button placement...
topLeft = CGPointMake(topLeft.x, topLeft.y + height + buffer);
}
// Increment topLeft to the next "column" for correct button placement...
topLeft = CGPointMake(topLeft.x + width + buffer, margin);
}
// So, I'm assuming your "add columns" button will be placed equivalent to
// "(columnCount + 1, 1)", or at the top of the screen, just to the right
// of the right-most column of buttons
// [self addButtons] is the property to the UIButton that calls [self addFourMoreColumns]
[[self addButtons] setFrame:CGRectMake(topLeft.x, topLeft.y, [[self addButtons] frame].size.width, [[self addButtons] frame].size.height)];
// Now, update the view that holds the buttons to give it a new size (based on the buttons added)
CGRect viewFrame = CGRectMake(0, 0, CGRectGetMaxX([[self addButtons] frame]) + buffer, 20 + ([[self buttonsArray] count] * (height + buffer)));
[[self buttonsView] setFrame:viewFrame];
[[self scrollView] setContentSize:viewFrame.size];
// Redraw the view...
[[self view] setNeedsDisplay];
}
- (IBAction)addFourMoreColumns {
[self setNumColumns:[self numColumns] + 4];
[self layoutButtons];
}
// Shows in the log which button you pressed: "(col, row)"
- (void)buttonPressed:(id)sender {
for (int i = 0; i < [[self buttonsArray] count]; i = i + 1) {
NSMutableArray *col = [[self buttonsArray] objectAtIndex:i];
for (int j = 0; j < [col count]; j = j + 1) {
if (sender == [col objectAtIndex:j]) {
NSLog(#"button (%d,%d) pressed", i + 1, j + 1);
break;
}
}
}
}
#end
Xib setup is basic with:
View // tied to [ViewController view]
|
--->ScrollView // tied to [ViewController scrollView]
|
--->View // tied to [ViewController buttonsView]
|
--->UIButton // tied to [ViewController addButtons]
this can be achieved using the scroll view UIScrollView and using two for loops to add required buttons at run time like
for(int col = 0; col < [max column]; col++)
{
for(int row = 0; row < [max row]; row++)
{
//add buttons assigning them proper frame size
}
}
You will need to add buttons programmatically using 2 loops. Also to identify the buttons assign them tags which are a number made by combination of column number and row number.
Eg:- button at row 1 and column 1 may have tag as 11.
Using the tag values you can identify which button is clicked and perform corresponding action.
Related
I am working on a view which displays multiple images on Imageview and the count of the images is dynamic.
I am getting those images through URL and the URL's string are stored in an array.
I have taken a for loop for creating a new imageview for every single image to display on it. The frame of the imageview is set on the basis of the size of the images. but the problem is that, when the count of images goes beyond 5, the sixth image goes out of the screen. But i want it to move downwards if there are more than 5 images in the array.
Here below is my code for displaying images on imageview using for-loop
int x=0;
for (int i=0;i<[items count]; i++)
{
NSLog(#"image name %#",[items objectAtIndex:i]);
NSLog(#"image url %#",[NSURL URLWithString:[NSString stringWithFormat:#"%#",[items objectAtIndex:i]]]);
NSString* urlTextEscaped = [[NSString stringWithFormat:#"%#",[items objectAtIndex:i]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *imgData=[NSData dataWithContentsOfURL:[NSURL URLWithString:urlTextEscaped]];
UIImageView *imgV = [[UIImageView alloc]initWithImage:[UIImage imageWithData:imgData]];
imgV.frame=CGRectMake(x, 0, 50, 50);
[cell.contentView addSubview:imgV];
x=x+imgV.frame.size.width+10;
}
Shift as this images
int x=0;
int y =0;
for (int i=0;i<[items count]; i++)
{
NSLog(#"image name %#",[items objectAtIndex:i]);
NSLog(#"image url %#",[NSURL URLWithString:[NSString stringWithFormat:#"%#",[items objectAtIndex:i]]]);
NSString* urlTextEscaped = [[NSString stringWithFormat:#"%#",[items objectAtIndex:i]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *imgData=[NSData dataWithContentsOfURL:[NSURL URLWithString:urlTextEscaped]];
UIImageView *imgV = [[UIImageView alloc]initWithImage:[UIImage imageWithData:imgData]];
imgV.frame=CGRectMake(x, y, 50, 50);
[cell.contentView addSubview:imgV];
if(x>cell.frame.size.width)
{
x =0;
y= y+imgV.frame.size.height+10;
}
else
{
x=x+imgV.frame.size.width+10;
}
}
You can display the images inside a scrollview or a tableview so that when there are many images and you are not able to fit all those images in the screen. Then it will have the scroll functionality.
If you are using storyboards, add a scrollview to your viewController's view and then use then this sample code should help you.
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setAutomaticallyAdjustsScrollViewInsets:YES];
[self addView:15];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) addView:(NSInteger)numberOfViews
{
double x =10 , y=10;
for (NSInteger i=1; i <= numberOfViews; i++) {
UIView * view = [[UIView alloc] initWithFrame:CGRectMake(x, y, 50, 50)];
y = y+70;
[view setBackgroundColor:[UIColor purpleColor]];
[_scrollView addSubview:view];
}
}
#end
Note: In interface builder,click your scrollview, then Attributes inspector and check (tick) the Bounce Vertically option under Scroll View section
when I click on button I get only 1 extra view, but I want a the view to be added dynamically on number of click, (like for loop)...Kindly help me with this code, Thanks in Advance,
this is just a part of my code
if (boolVal == true) {
CGRect newFrameC = CGRectMake(_centreView.frame.origin.x, _centreView.frame.origin.y, _centreView.frame.size.width, 50);
CGRect newFrameL1 = CGRectMake(_label2.frame.origin.x, _label2.frame.origin.y+50, _label2.frame.size.width, 50);
_centreView.frame = newFrameC;
_label2.frame = newFrameL1;
boolVal = false;
}else if (boolVal == false){
CGRect newFrameC = CGRectMake(_centreView.frame.origin.x, _centreView.frame.origin.y, _centreView.frame.size.width, 1);
CGRect newFrameL1 = CGRectMake(_label2.frame.origin.x, _label2.frame.origin.y-50, _label2.frame.size.width, 50);
_centreView.frame = newFrameC;
_label2.frame = newFrameL1;
boolVal = true;
}
DummyViewController.m
#import "DummyViewController.h"
#import "ExtraView.h"
#interface DummyViewController ()
#property (nonatomic) unsigned int numberOfExtraViews;
#property (nonatomic, strong) NSMutableArray<ExtraView*>* extraViews;
#property (nonatomic, strong) UILabel* label1;
#property (nonatomic, strong) UILabel* label2;
#end
#implementation DummyViewController
-(void) removeExtraViews{
for (ExtraView* extraView in _extraViews){
[extraView removeFromSuperview];
}
[_extraViews removeAllObjects];
}
-(CGRect) getExtraViewFrame{
CGRect extraViewFrame = _label1.frame;
if (_numberOfExtraViews > 0) {
extraViewFrame = [_extraViews lastObject].frame;
}
extraViewFrame.origin.x += extraViewFrame.size.height;
return extraViewFrame;
}
- (void) addExtraViews{
//[self removeExtraViews];
int numberOfExtraViewsToDraw = _numberOfExtraViews - _extraViews.count;
for (int iterator = 0; iterator < numberOfExtraViewsToDraw; iterator ++){
CGRect extraViewFrame = [self getExtraViewFrame];
ExtraView *extraView = [[ExtraView alloc]initWithFrame:extraViewFrame];
[self.view addSubview:extraView];
[_extraViews addObject:extraView];
}
if (numberOfExtraViewsToDraw > 0) {
CGRect label2Frame = [_extraViews lastObject].frame;
label2Frame.origin.x += label2Frame.size.height;
_label2.frame = label2Frame;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self addExtraViews];
//rest of your code
}
//Use this code in initWithNib/initWithCoder. Don't copy paste the same
-(instancetype)init{
self = [super init];
if (nil != self){
_numberOfExtraViews = 0;
_extraViews = [[NSMutableArray alloc]init];
}
return self;
}
//the function that gets hit when the button is tapped.
- (void) onButtonTap{
_numberOfExtraViews++;
[self.view setNeedsDisplay];
}
Note the following:
I do not understand what the boolVal is, but if it keeps toggling upon the click of the button, you'll never have multiple views.I assumed it toggles, so I havent used it at all.
Use the init code in initWithNib/initWithCoder. Don't copy paste the same. Don't overwrite the given code. Just append to your current init.
In my code onButtonTap is the function that gets hit when the button is tapped.
I've done it by ensuring viewDidLoad method gets hit agin. But I dont think it's necessary. You can also do this:
//the function that gets hit when the button is tapped.
- (void) onButtonTap{
_numberOfExtraViews++;
[self addExtraViews];
}
What has been done:
Let me call what you call as centerView as extraView. There will be many extraViews, so I'll create an array for the same (extraViews).
The count of the number of views is stored in numberOfExtraViews. Initiated to 0 in init.
Whenever the button is tapped, we increase the count and call view's setNeedsDisplay, which in turn hits the viewDidLoad method
In viewDidLoad, we add the extraViews.
Consider the following situation:
I have this myViewcontroller. On the Storyboard I have added a UIViewController, defined it as a myViewcontroller, with a couple of UILabels on them. I have properly connected them to the IBOutlets.
Then, I have a scrollview. I add instances of myViewcontroller to that scrollview as such:
- (void)configureInfoViewController
{
for (int i = 0; i < [Lists count]; i++) {
myViewcontroller *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"myLabelLayout"];
[self addChildViewController:vc];
}
}
- (void)configureScrollView
{
for (int i = 0; i < [self.childViewControllers count]; i++) {
CGRect frame = theScrollView.frame;
...
[[[self.childViewControllers objectAtIndex:i] view] setFrame:frame];
[theScrollView addSubview:[[self.childViewControllers objectAtIndex:i] view]];
}
}
So, I have as many subviews in theScrollview as there are objects in Lists. So far so good.
But now, I want to set the text of a label in one of these subviews. I want to accomplish that by pushing a UIButton on that very same instance of that myViewcontroller.
The question is: how do I set that myLable.title?
I hope this makes sense.
I think the REAL question is, that bothers me:
How do I know on what subview my UIButton resides. I could then send that metadata to other methods. Thanks.
I am assuming you have created outlets for your labels and when you push the button can't you just access the label and change its text in the button's IBAction method?
Here is what you need,
#import "MyViewcontroller.h"
#implementation MyViewcontroller
- (IBAction)myButtonAction:(id)sender {
_myLabel.text = #"My custom value";
}
#end
FYI, your InfoViewController your configureScrollView method should be like this. I don't see your scroll view content size adjusted in your code.
- (void)configureScrollView {
for (int i=0; i<self.childViewControllers.count; i++) {
CGRect frame = theScrollView.frame;
// scrolling vertically here
frame.origin.y = CGRectGetHeight(frame) * i;
UIView *view = [[self.childViewControllers objectAtIndex:i] view];
[view setFrame:frame];
[theScrollView addSubview:view];
}
theScrollView.contentSize = CGSizeMake(CGRectGetWidth(theScrollView.frame), CGRectGetHeight(theScrollView.frame) * self.childViewControllers.count);
}
I'm trying to implement a graph (UIView) like this:
My current approach is to draw the rounded rects with UIBezierPath inside drawRect, store a pointer to every path in a private NSMutableArray and in my View Controller send a message to update the graph with the new value. (https://gist.github.com/gverri/f238ad17f90013bfc832)
But somehow I can't send a message to my view to change the colors of the paths.
.
I'm still a relative beginner and I'm sure I'm trying to reinvent the wheel somehow. Or just missing something obvious.
Should I redraw the graph at every update (1s ~ 2s)?! Should I use a special framework to do this?
Edit:
After digging a little I found out that I can't save a pointer to a path. After drawRect my pointers disappear from the array.
It seems I would need to use Core Graphics do make it work with this approach.
Is there an easier alternative?
I made a test app that uses a column view with 7 rounded rect subviews to implement the graph. Here's the ColumnView class (a subview of UIView),
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSMutableArray *temp = [NSMutableArray new];
_offColor = [UIColor colorWithRed:238/255.0 green:230/255.0 blue:238/255.0 alpha:1];
_onColor = [UIColor colorWithRed:154/255.0 green:102/255.0 blue:155/255.0 alpha:1];
for (int i=0; i<7; i++) {
UIView *roundRect = [[UIView alloc] initWithFrame:CGRectMake(0, i*34, frame.size.width -2, (frame.size.height/7) - 4)];
roundRect.backgroundColor = _offColor;
roundRect.layer.cornerRadius = 3;
[temp insertObject:roundRect atIndex:0];
[self addSubview:roundRect];
}
_columns = [NSArray arrayWithArray:temp];
}
return self;
}
-(void)setNumberOfDarkRects:(NSInteger) num {
[self.columns enumerateObjectsUsingBlock:^(UIView *sub, NSUInteger idx, BOOL *stop) {
if (idx + 1 <= num) {
sub.backgroundColor = self.onColor;
}else{
sub.backgroundColor = self.offColor;
}
}];
}
This is how I create the grid, and test updating the columns,
#interface ViewController ()
#property (strong,nonatomic) NSMutableArray *columns;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.columns = [NSMutableArray new];
for (int i=0; i<21; i++) {
ColumnView *column = [[ColumnView alloc] initWithFrame:CGRectMake(i * 22 + 10, 50, 22, 240)];
[self.view addSubview:column];
[self.columns addObject:column];
}
[self performSelector:#selector(doRandomColoring) withObject:nil afterDelay:2];
}
-(void)doRandomColoring {
for (ColumnView *col in self.columns) {
[col setNumberOfDarkRects:arc4random_uniform(8)];
}
[self performSelector:#selector(doRandomColoring) withObject:nil afterDelay:.2];
}
I am writing a simple application that consists of 2 UIImageViews and 2 UIButtons. The UIImageViews are placed on top of the UIButtons.
I have 1 UIImage that appears on a random UIImageView and disappears after 2 seconds. The user then has to tap on the button they think the image appeared on.
I shuffle the UIImageView array and use the first element (index 0) for the image to be displayed on. When shuffling I keep track of which element (i.e from which index. lets say from index "n") was placed on index 0 of the array.
However, when a button is pressed i compare the id of the button pressed with the id of the button with index n. (because that is the index of the random UIImageView).
But for some reason it is not working. :(
Here is my code: (i didn't upload the .h file. it just contains declarations.)
The code below doesn't produce any error/warning messages. It just doesn't output the result I want.
#interface ViewController ()
#property (strong, nonatomic) NSMutableArray* tempButton;
#property (strong, nonatomic) NSMutableArray *tempViews;
#property (strong, nonatomic) UIImage *myImage;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_myImage = [UIImage imageNamed:#"hello"];
_tempButton = [[NSMutableArray alloc] initWithObjects:_button1, _button2, nil];
_tempViews = [[NSMutableArray alloc]initWithObjects:_image1, _image2, nil];
[self displayonView];
}
-(void)displayToFindSymbol{
[_toFindImage setImage:_myImage];
_toFindImage.contentMode = UIViewContentModeScaleAspectFit;
}
- (IBAction)pressed:(id)sender {
UIButton *result = (UIButton *)sender;
if (result == _tempButton[_correctButton]) {
NSLog (#"Correct");
}
else if (result != _tempButton[_correctButton]){
NSLog (#"Incorrect");
}
}
-(NSMutableArray *)shuffleViews{
NSUInteger count = _tempViews.count;
int n;
for (int i=count-1; i>=0; --i) {
n = arc4random() % (i + 1);
[_tempViews exchangeObjectAtIndex:n withObjectAtIndex:i];
}
_correctButton = n;
return _tempViews;
}
-(void)displayonView{
NSMutableArray *tempArray;
tempArray = [self shuffleViews]; // shuffle views
_correctImage = [tempArray objectAtIndex:0];
_correctImage.contentMode = UIViewContentModeScaleAspectFit;
[_correctImage setImage:_myImage];
NSTimer* myTImer;
myTImer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(hideLabel) userInfo:nil repeats:NO];
}
-(void) hideLabel{
_correctImage.hidden = YES;
for (UIButton *each in self.tempButton) {
each.enabled = YES;
}
[self displayToFindSymbol];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
Use tags instead, set a unique tag to both of the buttons, and set the tag of image to the button that the image will belong, later on, compare the tag of both image and the button to check if they are the same or not.
https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/occ/instp/UIView/tag
I'm kinda new to objective-c and iOS aswell, but i don't think you should compare like this:
result == _tempButton[_correctButton]
try this comparison instead:
[result isEqual:_tempButton[_correctButton]]