How to make grid of elements in iOS? - ios

So I'm about to make an app for iOS in xCode (go figure), and I am curious as to what people would suggest to do here.
My goal is to create a grid of buttons mxn in dimensions. I recently made a tic tac toe game variation where I had an overall 9x9 grid of buttons. It was very tedious work to create each button.
Would there be an easier way to create all these buttons through code?

Use a for loop to create the buttons. May be something of the following sorts to create a buttons for 3X3 grid
CGFloat xAxis,yAxis,bWidth,bHeight;
xAxis = 0.0;
yAxis = 0.0;
bWidth = 150.0;
bHeight = 44.0;
int numberOfRows = 3;
int numberOfColumns = 3;
int number = 1;
for(int i=0;i<numberOfRows;i++)
{
for(int j=0;j<numberOfColumns;j++)
{
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(xAxis, yAxis, bWidth, bHeight)];
button.titleLabel.text = [NSString stringWithFormat:#"%#",number];
number++;
[self.view addSubview:button];
xAxis = xAxis + bWidth + 5.0;
}
xAxis = 0;
yAxis = yAxis + bHeight + 5.0;
}

IBOutletCollections are a good candidate here, if you have a reasonable number of buttons. You can then loop through all of them to apply the same properties.

Related

iOS: Two Bar Charts together

I am using charts library (Charts)
I am developing the application allows me to show number of guests in restaurant realtime and compare data between different days.
For example look at this picture
The dashed line means data for the compared to date. I would like to create BarChart like this but library only allows me to show 4 grouped bars. I want to show dashed bars above colored with small offset. Help me out please
My code is:
for (int i = 0; i < days.count; i++) {
BarChartDataEntry *guysEntry = [[BarChartDataEntry alloc] initWithX:i y:[guys[i] integerValue]];
[guysChartDataArray addObject:guysEntry];
BarChartDataEntry *girlsEntry = [[BarChartDataEntry alloc] initWithX:i y:[girls[i] integerValue]];
[girlsChartDataArray addObject:girlsEntry];
BarChartDataEntry *guysCompareToEntry = [[BarChartDataEntry alloc] initWithX:i y:[guysCompareTo[i] integerValue]];
[guysCompareToChartDataArray addObject:guysCompareToEntry];
BarChartDataEntry *girlsCompareToEntry = [[BarChartDataEntry alloc] initWithX:i y:[girlsCompareTo[i] integerValue]];
[girlsCompareToChartDataArray addObject:girlsCompareToEntry];
}
BarChartDataSet *guysChartDataSet = [[BarChartDataSet alloc] initWithValues:guysChartDataArray label:#"Guys"];
guysChartDataSet.colors = #[[UIColor maleColor]];
guysChartDataSet.valueTextColor = [UIColor clearColor];
BarChartDataSet *girlsChartDataSet = [[BarChartDataSet alloc] initWithValues:girlsChartDataArray label:#"Girls"];
girlsChartDataSet.colors = #[[UIColor femaleColor]];
girlsChartDataSet.valueTextColor = [UIColor clearColor];
LineChartXAxisFormatter *barGraphXFormatter = [[LineChartXAxisFormatter alloc] init];
barGraphXFormatter.xLabels = [days mutableCopy];
self.barChartView.xAxis.valueFormatter = barGraphXFormatter;
self.barChartView.xAxis.centerAxisLabelsEnabled = YES;
self.combinedChartView.xAxis.valueFormatter = barGraphXFormatter;
self.combinedChartView.xAxis.centerAxisLabelsEnabled = YES;
float groupSpace = 0.06f;
float barSpace = 0.02f;
float barWidth = 0.45f;
BarChartDataSet *guysCompareToChartDataSet = [[BarChartDataSet alloc] initWithValues:guysCompareToChartDataArray label:#"Guys (Compare)"];
guysCompareToChartDataSet.colors = #[[UIColor clearColor]];
guysCompareToChartDataSet.barBorderWidth = 1.f;
guysCompareToChartDataSet.barBorderColor = [UIColor grayColor];
guysCompareToChartDataSet.isDashedBorder = YES;
guysCompareToChartDataSet.axisDependency = AxisDependencyLeft;
guysCompareToChartDataSet.valueTextColor = [UIColor clearColor];
BarChartDataSet *girlsCompareToChartDataSet = [[BarChartDataSet alloc] initWithValues:girlsCompareToChartDataArray label:#"Girls (Compare)"];
girlsCompareToChartDataSet.colors = #[[UIColor clearColor]];
girlsCompareToChartDataSet.barBorderWidth = 1.f;
girlsCompareToChartDataSet.barBorderColor = [UIColor grayColor];
girlsCompareToChartDataSet.isDashedBorder = YES;
girlsCompareToChartDataSet.axisDependency = AxisDependencyLeft;
girlsCompareToChartDataSet.valueTextColor = [UIColor clearColor];
NSArray *dataSets = #[guysChartDataSet, girlsChartDataSet, guysCompareToChartDataSet, girlsCompareToChartDataSet];
BarChartData *barChartData = [[BarChartData alloc] initWithDataSets:dataSets];
barChartData.barWidth = barWidth;
CGFloat initialValue = 0;
CGFloat groupCount = days.count;
self.barChartView.xAxis.axisMinimum = initialValue;
self.barChartView.xAxis.axisMaximum = initialValue + [barChartData groupWidthWithGroupSpace:groupSpace barSpace: barSpace] * groupCount;
[barChartData groupBarsFromX:0 groupSpace:groupSpace barSpace:barSpace];
self.barChartView.data = barChartData;
I want to make something like:
Now that you've changed your question, this requires a different solution.
As I mentioned in the comments before, you need to manually calculate each bar's position. Then, don't use the grouping feature, because you've already grouped them how you want.
// Determine bar sizing and spacing parameters.
int barsPerGroup = 2;
double targetGroupWidth = 1;
double barSpacing = 0.2;
double groupSpacing = 0.3;
double barWidth = (targetGroupWidth - groupSpacing - barSpacing * barsPerGroup) / barsPerGroup;
double compareBarOffset = barWidth / 3;
for (int i = 0; i < entryCount; i++) {
// Determine X position for each bar
// NOTE: This is the most important step!
double groupStartPosition = targetGroupWidth * i;
double group1X = groupStartPosition + barWidth / 2;
double group1CompareX = group1X + compareBarOffset;
double group2X = group1X + barWidth + barSpacing;
double group2CompareX = group2X + compareBarOffset;
// Create data entries positioned at values calculated by previous step
NSNumber *group1Value = group1[i];
NSNumber *group1CompareValue = group1Compare[i];
NSNumber *group2Value = group2[i];
NSNumber *group2CompareValue = group2Compare[i];
BarChartDataEntry *group1DataEntry = [[BarChartDataEntry alloc] initWithX:group1X
y:[group1Value doubleValue]];
BarChartDataEntry *group1CompareDataEntry = [[BarChartDataEntry alloc] initWithX:group1CompareX
y:[group1CompareValue doubleValue]];
BarChartDataEntry *group2DataEntry = [[BarChartDataEntry alloc] initWithX:group2X
y:[group2Value doubleValue]];
BarChartDataEntry *group2CompareEntry = [[BarChartDataEntry alloc] initWithX:group2CompareX
y:[group2CompareValue doubleValue]];
// ...
}
// Create Data Sets, set styles, etc.
// ...
// Do NOT use this method because bars are already grouped.
//[barChartView groupBarsFromX:0 groupSpace:groupSpacing barSpace:barSpacing];
RESULT:
Note: I can't find the property isDashedBorder, so I don't know what version of the library you're using. Instead, I just set clear bars with a solid gray border.
With Charts, it looks you can:
Stack data into the same bar.
Group bars so that they overlap.
It also looks like you can't:
Give each data entry in the stacked bar a separate border color. (If you supply a border color, it will apply to the entire bar.)
Note about documentation
Do remember that Charts is modeled after MPAndroidChart and follows its API very closely.
Therefore, if you need help, refer to their documentation. The Java syntax is a bit foreign to me, but with the help of Xcode's autocomplete, I was able to find everything I needed.
Code:
Please take note of three specific parts:
How to create stacked data entries.
Inability to set separate borders per item in stacked bar.
How to overlap bars by providing a negative bar spacing.
I'm by no means an expert in this library, but simply reading the documentation, I was able to put this together.
// MARK: Data Entries
for (int i = 0; i < days.count; i++) {
NSNumber *guyValue = guys[i];
NSNumber *girlValue = girls[i];
// NOTE 1: To get "stacked" bars, use the initializer `initWithX:yValues:`
BarChartDataEntry *guyGirlDataEntry = [[BarChartDataEntry alloc] initWithX:i
yValues:#[guyValue, girlValue]];
NSNumber *guyCompareToValue = guysCompareTo[i];
NSNumber *girlCompareToValue = girlsCompareTo[i];
BarChartDataEntry *guyGirlCompareToEntry = [[BarChartDataEntry alloc] initWithX:i
yValues:#[guyCompareToValue, girlCompareToValue]];
[guyGirlChartDataArray addObject:guyGirlDataEntry];
[guyGirlCompareToChartDataArray addObject:guyGirlCompareToEntry];
}
// MARK: Data Sets
BarChartDataSet *guysGirlsChartDataSet = [[BarChartDataSet alloc] initWithValues:guyGirlChartDataArray label:nil];
BarChartDataSet *guysGirlsCompareToChartDataSet = [[BarChartDataSet alloc] initWithValues:guyGirlCompareToChartDataArray label:nil];
BarChartData *data = [[BarChartData alloc] initWithDataSets:#[guysGirlsCompareToChartDataSet, guysGirlsChartDataSet]];
// MARK: Styling
guysGirlsChartDataSet.stackLabels = #[#"Guys", #"Girls"];
guysGirlsChartDataSet.colors = #[[UIColor maleColor],
[UIColor femaleColor]];
// NOTE 2: Unfortunately, you can only set one border color to the bar.
// It seems, you won't be able to use separate dashed borders in the same bar, like you want.
// For demonstration purposes, I've simply made the comparison colors 50% transparent.
guysGirlsCompareToChartDataSet.stackLabels = #[#"Guys (Compare)", #"Girls (Compare)"];
guysGirlsCompareToChartDataSet.colors = #[[[UIColor maleColor] colorWithAlphaComponent:0.5],
[[UIColor femaleColor] colorWithAlphaComponent:0.5]];
// Sets the x axis label interval, so it doesn't show labels like "0.9"
barChartView.xAxis.granularity = 1;
// Centers the x axis label over the bar group, rather than on the grid line.
barChartView.xAxis.centerAxisLabelsEnabled = YES;
// MARK: Displaying Chart Data
barChartView.data = data;
// Grouping
// I still can't seem to set this properly.
// Even though I tell it to fit the bars exactly and don't show below 0 on the x axis,
// the chart still shows below zero and the right-most bar gets cut off.
// If this isn't a bug, then perhaps you can find the answer to this elsewhere.
barChartView.fitBars = YES;
[barChartView setVisibleXRangeMinimum:0];
// Calculate bar grouping parameters.
NSInteger barCountPerGroup = data.dataSetCount;
double barWidth = 0.4;
// NOTE 3: Negative bar spacing will make the bars overlap.
double barSpace = -0.3;
// According to documentation, total group width (bars and spacing) must add up to 1
double groupSpace = 1 - (barWidth + barSpace) * barCountPerGroup;
// Set the grouping parameters.
data.barWidth = barWidth;
[barChartView groupBarsFromX:0 groupSpace:groupSpace barSpace:barSpace];
// Refresh the chart (if necessary)
[barChartView notifyDataSetChanged];
Output:
The styling isn't very good, but the bars are grouped and overlap like you want.
I trust you can find help with the styling elsewhere.

How to generate a line of images programmatically on Xcode

I am trying to make an iOS game and it requires for the players to catch as many coins as it can. I know to show the coins programmatically, but how do I make a line of coins with ease. Similarly like the game line runner where the runner has to catch the coins or jetpack joyride?
I'm thinking of something like
The game is landscape
randomPlace = arc3random()%315;
coinImage.center = CGPointMake(coinImage.center.x - 1, randomPlace);
coinImage1.center = CGPointMake(coinImage1.center.x - 1, randomPlace);
I don't even know if I'm on the right track but can anyone help me? thank you!
You could use something like this to create 5 images in a horizontal line:
//Generate your variables
float width = 15, height = 15;
float originX = 20, originY = 50, spacing = 5;
//Create 5 image views
for (int i = 0; i < 5; i++) {
UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(originX + ((spacing + width) * i), originY, width, height)];
[imgV setImage:[UIImage imageNamed:#"YourImage.png"]];
[self.view addSubview:imgV];
}
Which will produce something like this:

Spawn UIImageView So That It Does Not Intersect With Existing UIImageViews

Basically, I have an app where a user dodges bombs and collects coins. When a user collects a coin, another coin is spawned. I want each coin to not spawn on a bomb. (By the way these are all uiimageviews). In the code below, I have an array of my bombs called bombArray and the UIImageview called "one" is the coin UIImageView. I know the code below doesn't work, but what other method(s) could I use? Thanks, and here is the code:
UIImageView *one = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"goldCoin.png"]];
CGRect rectOne = CGRectMake(arc4random() % (900), arc4random() % (700), 40, 40);
[one setFrame:rectOne];
[self.view addSubview:one];
for (UIImageView* two in bombArray)
{
while (CGRectIntersectsRect(two.frame, one.frame))
{
one.center=CGPointMake(arc4random() % (900), arc4random() % (700));
}
}
*Note: I completely understand why the code above does not work, but I cannot find another solution to my problem.
First of all to make such a thing I'd use the new SpriteKit that gives you physics (and thus collision detection) out of the box.
But if you want to do this with standard UIViews what you can do for being absolutely sure that images don't collide between them is to divide the container in a grid and then loop through each cell of your grid and "flip a coin" to randomly put a coin or not in that cell. Then if you want to fill more space instead of dividing for rectangular cells you can divide for hexagonal cells, in this way you will cover more space.
So a fast and absolutely not too much checked version could be something like this:
int x = 0;
int y = 0;
int side = 40;
int it = 0;
do {
do {
CGRect imageFrame = CGRectMake(x, y, side, side);
UIImageView* imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.borderWidth = 1.0;
imageView.layer.cornerRadius = side / 2;
imageView.layer.masksToBounds = YES;
[self.containerView addSubview:imageView];
BOOL flip = arc4random() & 0x1;
if (flip) {
[imageView setImage:[UIImage imageNamed:#"coin_image"]];
}
} while ((x+=side) < (self.containerView.frame.size.width - side / 2));
it++;
if (it % 2 != 0) {
x = side / 2;
} else {
x = 0;
}
} while ((y+=(side * 0.85)) < (self.containerView.frame.size.height - side) );
Mind that this is NOT efficient, it is executed in the main thread and doesn't take in account possible (actually probable) memory constraints.

IOS: organize UIButton spaces

I have three button in my view and sometimes I should hide one button or two button because I don't use them; I can insert them in a toolbar or in a subview but I don't know a fast solution to fair distance buttons. For example:
Three buttons:
Two button:
One button:
is there a fast solution to distance button in a dynamic solution, without take their position?
float width = 768.0f;
int numberOfButtons = 3;
float buttonWidth = 100.0f;
float buttonHeight = 40.0f;
float spaceX = floorf((width - (numberOfButtons * buttonWidth)) / (numberOfButtons + 1));
float x,y,w,h;
for (int i = 0; i < numberOfButtons; i++)
{
x = spaceX + (spaceX * i);
y = 100.0f;
w = buttonWidth;
h = buttonHeight;
UIButton * b = [[UIButton alloc] initWithFrame:CGRectMake(x,y,w,h)];
[self.view addSubview:b];
}
Where width is the gray view.
I would use a UICollectionView. It would allow you to dynamically add and remove buttons and it would handle all appropriate layout.

Putting labels in slices of donut style CPTPiePlot, they don't line up right

Im trying to draw a donut pie chart on ios with core-plot, with one chart inside of another. Each slice has the same width, but the slices are color coded. I'm trying to place a colored dot in the center of each slice, but I can't figure out how to get the position of the dots correct.
I've managed to get them roughly centered using the labelOffset property of the pie chart.(currently im using a label offset of -35, determined by trial and error). But I can't mange to get them exactly centered, and in the innermost graph they can end up far enough off that they are in the wrong slice.
I have tried setting the rectAnchor property of the text layer, and the padding, but neither one seems to have any effect.
Code for creating the plots:
CGFloat startAngle = [self degreesToRadians:90 + ((360 / [self.dangerRoseData numberOfSectors]) /2.0)];
NSInteger levels = [self.dangerRoseData numberOfLevels];
for(int i = 0; i < levels; i++){
CGFloat radiusD = (i + 1.0)/ levels;
CGFloat radius = MIN(radiusD * (graphHostingView.frame.size.height - 2 * graph.paddingLeft) / 2.8,
radiusD * (graphHostingView.frame.size.width - 2 * graph.paddingTop) / 2.8);
CPTPieChart *lastPie = [plots lastObject];
CGFloat innerRadius;
if(lastPie != nil)
innerRadius = lastPie.pieRadius;
else
innerRadius = 0;
CPTPieChart *piePlot = [[CPTPieChart alloc] init];
piePlot.backgroundColor = [UIColor clearColor].CGColor;
piePlot.dataSource = self;
piePlot.delegate = self;
piePlot.pieRadius = radius;
piePlot.pieInnerRadius = innerRadius;
piePlot.identifier = [self.dangerRoseData nameForLevel:i];
piePlot.borderLineStyle = lineStyle;
piePlot.startAngle = startAngle;
piePlot.sliceDirection = CPTPieDirectionClockwise;
piePlot.labelOffset = -35;//brute force trial and error
piePlot.labelRotationRelativeToRadius = NO;//new property after 1.0
[graph addPlot:piePlot];
[plots addObject:piePlot];
}
and for the labels:
-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index{
....
CPTMutableTextStyle *textStyle = [[CPTMutableTextStyle alloc] init];
textStyle.color = color;
textStyle.fontSize = 36;
CPTTextLayer *label = [[CPTTextLayer alloc] initWithText:#"•" style:textStyle];
return label;
}
I Tried updating to the newest version of core-plot, which adds a new property to CPTPiePlot labelRotationRelativeToRadius which seems like it ought to be the answer to this problem, but it's not. With the new version im not able to get the dots positioned anywhere close to the correct spot.
Am I missing something with positioning the labels?
Am I using totally the wrong approach to putting the dots in the slices?
Well, I decided to give a try using images instead of text labels, and the positioning works just fine, it doesn't seem to be at all erratic like text labels.
In order to be able to fit the dots in the inner most pie I also had to change the sizes, so instead of taking up equal amounts of the total radius, the inner pie takes twice the space of the rest.
Code for generating the plots:
NSInteger levels = [self.dangerRoseData numberOfLevels];
for(int i = 0; i < levels; i++){
CPTPieChart *lastPie = [plots lastObject];
CGFloat innerRadius;
if(lastPie != nil)
innerRadius = lastPie.pieRadius;
else
innerRadius = 0;
CGFloat ringThickness;
ringThickness = ( (graphHostingView.frame.size.width - 2 * graph.paddingTop) / ((levels + 1) * 2) ) * .85;
if(i == 0)//make the inner circle twice the thickness of the rings
ringThickness += ringThickness;
CGFloat radius = innerRadius + ringThickness;
int labelOffset = -14;
if(i == 0)
labelOffset = -15;
CPTPieChart *piePlot = [[CPTPieChart alloc] init];
piePlot.backgroundColor = [UIColor clearColor].CGColor;
piePlot.dataSource = self;
piePlot.delegate = self;
piePlot.pieRadius = radius;
piePlot.pieInnerRadius = innerRadius;
piePlot.identifier = [self.dangerRoseData nameForLevel:i];
piePlot.borderLineStyle = lineStyle;
piePlot.startAngle = startAngle;
piePlot.sliceDirection = CPTPieDirectionClockwise;
piePlot.labelOffset = labelOffset;
[graph addPlot:piePlot];
[plots addObject:piePlot];
}
and for the labels:
-(CPTLayer *)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)index
{
...
CPTImageLayer *layer = [[CPTImageLayer alloc] initWithImage:[UIImage imageNamed:filename]];
layer.anchorPoint = CGPointMake(0.5, 0.5);
return layer;
}
Note that the CPTImageLayer is an addition to the standard core-plot to reduce the complexity of using image

Resources