I have been messing around with a way to justify align a collection of UIView subclasses within a containing view. I am having a little bit of trouble with the algorithm and was hoping someone could help spot my errors. Here is pseudocode of where I am now:
// 1 see how many items there are
int count = [items count];
// 2 figure out how much white space is left in the containing view
float whitespace = [containingView width] - [items totalWidth];
// 3 Figure out the extra left margin to be applied to items[1] through items[count-1]
float margin = whitespace/(count-1);
// 4 Figure out the size of every subcontainer if it was evenly split
float subcontainerWidth = [containingView width]/count;
// 5 Apply the margin, starting at the second item
for (int i = 1; i < [items count]; i++) {
UIView *item = [items objectAtIndex:i];
[item setLeftMargin:(margin + i*subcontainerWidth)];
}
The items do not appear to be evenly spaced here. Not even close. Where am I going wrong?
Here is a shot of this algorithm in action:
alt text http://grab.by/1Wcg
EDIT: The code above is pseudocode. I added the actual code here but it might not make sense if you are not familiar with the three20 project.
#implementation TTTabStrip (JustifiedBarCategory)
- (CGSize)layoutTabs {
CGSize size = [super layoutTabs];
CGPoint contentOffset = _scrollView.contentOffset;
_scrollView.frame = self.bounds;
_scrollView.contentSize = CGSizeMake(size.width + kTabMargin, self.height);
CGFloat contentWidth = size.width + kTabMargin;
if (contentWidth < _scrollView.size.width) {
// do the justify logic
// see how many items there are
int count = [_tabViews count];
// 2 figure out how much white space is left
float whitespace = _scrollView.size.width - contentWidth;
// 3 increase the margin on those items somehow to reflect. it should be (whitespace) / count-1
float margin = whitespace/(count-1);
// 4 figure out starting point
float itemWidth = (_scrollView.size.width-kTabMargin)/count;
// apply the margin
for (int i = 1; i < [_tabViews count]; i++) {
TTTab *tab = [_tabViews objectAtIndex:i];
[tab setLeft:(margin + i*itemWidth)];
}
} else {
// do the normal, scrollbar logic
_scrollView.contentOffset = contentOffset;
}
return size;
}
#end
I was able to get it to work on my own! I was applying the margin wrong to the elements. The issue is that I needed to apply the margin while considering the previous elements origin and width.
#implementation TTTabStrip (JustifiedBarCategory)
- (CGSize)layoutTabs {
CGSize size = [super layoutTabs];
CGPoint contentOffset = _scrollView.contentOffset;
_scrollView.frame = self.bounds;
_scrollView.contentSize = CGSizeMake(size.width + kTabMargin, self.height);
CGFloat contentWidth = size.width + kTabMargin;
if (contentWidth < _scrollView.size.width) {
// do the justify logic
// see how many items there are
int count = [_tabViews count];
// 2 figure out how much white space is left
float whitespace = _scrollView.size.width - contentWidth;
// 3 increase the margin on those items somehow to reflect. it should be (whitespace) / count-1
float margin = whitespace/(count-1);
// apply the margin
for (int i = 1; i < [_tabViews count]; i++) {
// 4 figure out width from the left edge to the right of the 1st element
float start = [[_tabViews objectAtIndex:i-1] frame].origin.x + [[_tabViews objectAtIndex:i-1] frame].size.width;
TTTab *tab = [_tabViews objectAtIndex:i];
[tab setLeft:(start + margin)];
}
} else {
// do the normal, scrollbar logic
_scrollView.contentOffset = contentOffset;
}
return size;
}
#end
coneybeare, thanks for figuring this out, but your solution doesn't really work as expected. It changes the position of tabs on the bar, but the spacing is not right. This seems to work better for me:
#import "TTTabStrip+Justify.h"
#import <Three20UI/UIViewAdditions.h>
// Width returned by [super layoutTabs] is always 10 px more than sum of tab widths
static CGFloat const kContentWidthPadding = 10;
// Adds fixed margin to left of 1st tab, right of last tab
static CGFloat const kHorizontalMargin = 5;
#implementation TTTabStrip (JustifyCategory)
- (CGSize)layoutTabs {
CGSize size = [(TTTabStrip*)super layoutTabs];
CGPoint contentOffset = _scrollView.contentOffset;
_scrollView.frame = self.bounds;
_scrollView.contentSize = CGSizeMake(size.width, self.height);
CGFloat contentWidth = size.width - kContentWidthPadding + 2 * kHorizontalMargin;
if (contentWidth < _scrollView.size.width) {
// do the justify logic
// see how many items there are
int count = [_tabViews count];
// calculate remaining white space
float whitespace = _scrollView.size.width - contentWidth;
// calculate necessary spacing between tabs
float spacing = whitespace / (count + 1);
// apply the spacing
for (int i = 0; i < count; i++) {
CGFloat lastTabRight = kHorizontalMargin;
if (i > 0) {
TTTab *lastTab = [_tabViews objectAtIndex:i-1];
lastTabRight = [lastTab right];
}
TTTab *tab = [_tabViews objectAtIndex:i];
[tab setLeft:(lastTabRight + spacing)];
}
} else {
// do the normal, scrollbar logic
_scrollView.contentOffset = contentOffset;
}
return size;
}
#end
Morgz, I also got several compiler errors. I needed to import UIViewAdditions.h and tell it that super is a TTTabStrip.
Related
I am having a strange problem in my project. What I want to do is that, a user will paint or draw using swipe over a image as overlay and I just need to crop the area from the image that is below the painted region. My code is working well only when the UIImage view that is below the paint region is 320 pixel wide i.e. width of iPhone. But If I change the width of the ImageView, I am not getting the desired result.
I am using the following code to construct a CGRect around the painted part.
-(CGRect)detectRectForFaceInImage:(UIImage *)image{
int l,r,t,b;
l = r = t = b = 0;
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8* data = CFDataGetBytePtr(pixelData);
BOOL pixelFound = NO;
for (int i = leftX ; i < rightX; i++) {
for (int j = topY; j < bottomY + 20; j++) {
int pixelInfo = ((image.size.width * j) + i ) * 4;
UInt8 alpha = data[pixelInfo + 2];
if (alpha) {
NSLog(#"Left %d", alpha);
l = i;
pixelFound = YES;
break;
}
}
if(pixelFound) break;
}
pixelFound = NO;
for (int i = rightX ; i >= l; i--) {
for (int j = topY; j < bottomY ; j++) {
int pixelInfo = ((image.size.width * j) + i ) * 4;
UInt8 alpha = data[pixelInfo + 2];
if (alpha) {
NSLog(#"Right %d", alpha);
r = i;
pixelFound = YES;
break;
}
}
if(pixelFound) break;
}
pixelFound = NO;
for (int i = topY ; i < bottomY ; i++) {
for (int j = l; j < r; j++) {
int pixelInfo = ((image.size.width * i) + j ) * 4;
UInt8 alpha = data[pixelInfo + 2];
if (alpha) {
NSLog(#"Top %d", alpha);
t = i;
pixelFound = YES;
break;
}
}
if(pixelFound) break;
}
pixelFound = NO;
for (int i = bottomY ; i >= t; i--) {
for (int j = l; j < r; j++) {
int pixelInfo = ((image.size.width * i) + j ) * 4;
UInt8 alpha = data[pixelInfo + 2];
if (alpha) {
NSLog(#"Bottom %d", alpha);
b = i;
pixelFound = YES;
break;
}
}
if(pixelFound) break;
}
CFRelease(pixelData);
return CGRectMake(l, t, r - l, b-t);
}
In the above code leftX, rightX, topY, bottomY are the extreme values(from CGPoint) in float that is calculated when user swipe their finger on the screen while painting and represents a rectangle which contains the painted area in its bounds (to minimise the loop).
leftX - minimum in X-axis
rightX - maximum in X-axis
topY - min in Y-axis
bottom - max in Y-axis
Here l,r,t,b are the calculated values for actual rectangle.
As expressed earlier, this code work well when the imageview in which paining is done is 320 pixels wide and is spanned throughout the screen width. But If the imageview's width is smaller like 300 and is placed to the center of the screen, the code give false result.
Note: I am scaling the image according to imageview's width.
Below are the NSLog output:
When imageview's width is 320 pixel (These are value for the component of color at matched pixel or non-transparent pixel):
2013-05-17 17:58:17.170 FunFace[12103:907] Left 41
2013-05-17 17:58:17.172 FunFace[12103:907] Right 1
2013-05-17 17:58:17.173 FunFace[12103:907] Top 73
2013-05-17 17:58:17.174 FunFace[12103:907] Bottom 12
When imageview's width is 300 pixel:
2013-05-17 17:55:26.066 FunFace[12086:907] Left 42
2013-05-17 17:55:26.067 FunFace[12086:907] Right 255
2013-05-17 17:55:26.069 FunFace[12086:907] Top 42
2013-05-17 17:55:26.071 FunFace[12086:907] Bottom 255
How can I solve this problem because I need the imageview in center with padding to its both side.
EDIT: Ok looks like my problem is due to image orientation of JPEG images(from camera). Png images are working good and are not affected with change in imageview's width.
But still JPEGs are not working even if I am handling the orientation.
First, I wonder if you're accessing something other than 32-bit RGBA? The index value for data[] is stored in pixelInfo then moves +2 bytes, rather than +3. That would land you on the blue byte. If your intent is to use RGBA, that fact would affect the rest of the results of your code.
Moving on, with an assumption that you were still getting flawed results despite having the correct alpha component value, it seems your "fixed" code would give Left,Right,Top,Bottom NSLog outputs with alpha values less than the full-on 255, something close to 0. In this case, without further code, I'd suggest your problem is within the code you use to scale down the image from your 320x240 source to 300x225 (or perhaps any other scaled dimensions). I could imagine your image having alpha values at the edge of 255 if your "scale" code is performing a crop rather than a scale.
I have a few functions that I'm using to draw different type of text. For example:
- (CGFloat)drawInformation:(CGContextRef)c withLeftCol:(NSArray *)leftCol rightcol:(NSArray *)rightCol atPoint:(CGPoint)point withLineSpacing:(CGFloat)lineSpacing {
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGFloat fontSize = 16.0;
UIFont *pFont = [UIFont fontWithName:#"Arial-BoldMT" size:fontSize];
CGFloat yOffset = 0;
for (NSString *leftColStr in leftCol) {
[leftColStr drawAtPoint:CGPointMake(point.x, point.y + yOffset) withFont:pFont];
yOffset += lineSpacing;
}
yOffset = 0;
for (NSString *rightColStr in rightCol) {
[rightColStr drawAtPoint:CGPointMake(rtStart, point.y + yOffset) withFont:pFont];
yOffset += lineSpacing;
}
return [leftCol count] > [rightCol count] ? [leftCol count] * (fontSize + lineSpacing) : [rightCol count] * (fontSize + lineSpacing);
}
I didn't include the whole method, but this is the gist of it. I basically take in an array of text, and draw them in two columns. I want to return the height of the area so I know how large it is and can draw the next piece of text below it.
One thing I found is even though I set my fontSize = 16, if I do a [#"text" sizeWithFont:pFont], I actually get 18 or something. As you can see, I add in some lineSpacing as well. So what I'm wondering is if I'm returning the correct amount in this case, and also, if I should be using the fontSize, or the [#"text" sizeWithFont:pFont].height in my return statement. In its current state, when I need to draw my next block of text at the new point, it is pretty off, like 100 points, and I'm not sure why there is such a discrepancy. Thanks!
Create separate variables for the y offset in your first loop and the y offset in your second loop. Then return MAX(yOffsetLeftCol, yOffsetRightCol).
I'm just getting started with iOS development. I would like to create a view that would contain 2 rows of image thumbnails as a preview. It would scroll horizontally. I'm wondering if the best approach would be to use 2 scrollviews or place them in a tableview with 2 rows.
If your thumbnail images and rows are essentially static, your best bet might be to drop your thumbnail image views directly into your scrollView. You can layout your thumbnail subviews into two rows and as many columns as necessary with some arbitrary calculations and avoid the need to use a UITableView (which works very well for scrolling rows, but doesn't natively support columns, and as a tool isn't imo the best for this particular job).
If you do elect to use a UITableView, I would put it into a single UIScrollview.... Unless you specifically seek two discretely scrolling rows, I wouldn't implement this with two UIScrollViews.
Here is a code snippet that suggests an approach. Consider 1)All subviews should be the same size 2)The subviews will "grid" themselves according to the bounds of the containing view. I wrote this snippet for a regular UIView (not a UIScrollView), but the concept will be the same: Consider size of scrollContent. Consider how many subviews will fit horizontally, and how many will fit vertically (hence, if you want 2 rows, the scroll view height should be just high enough to fully encompass the height of a single subview * 2):
- (void)layoutSubviews
{
//Lays out subviews in a grid to fill view.
float myWidth = self.bounds.size.width;
float myHeight = self.bounds.size.height;
if ([[self subviews] count] < 1)
{
return; //no subviews, must be added before layoutSubviews is called.
}
CGRect aSubviewRect = [[[self subviews] objectAtIndex:0] frame];
int numberOfColumns = (int)(myWidth / aSubviewRect.size.width);
float actualColumnWidth = myWidth / (float)numberOfColumns;
CGFloat paddingPerColumn = (actualColumnWidth - aSubviewRect.size.width);
int maxRows = (int)(myHeight / aSubviewRect.size.height);
int qtyOfViews = [[self subviews] count];
int numberOfRows = (qtyOfViews / numberOfColumns);
if ((qtyOfViews % numberOfColumns) > 0) //we need remainder row
{
++numberOfRows;
}
if (numberOfRows > maxRows)
{
numberOfRows = maxRows;
NSLog(#"More rows required than fit.");
}
float actualRowHeight = myHeight / (float)numberOfRows;
CGFloat paddingPerRow = (actualRowHeight - aSubviewRect.size.height);
for (UIView *aSubview in [self subviews])
{
int viewIndex = [[self subviews] indexOfObject:aSubview];
int rowIndex = (viewIndex / numberOfColumns);
CGFloat yOrigin = (roundf(((float)rowIndex * aSubviewRect.size.height) + (((rowIndex + 1) * paddingPerRow) / 2)));
int columnIndex = (viewIndex % numberOfColumns);
CGFloat xOrigin = (roundf(((float)columnIndex * aSubviewRect.size.width) + (((columnIndex + 1) * paddingPerColumn) / 2)));
CGRect frame = [aSubview frame];
frame.origin.y = yOrigin;
frame.origin.x = xOrigin;
[aSubview setFrame:frame];
}
}
i want to display many images like thumbnails in a scroll view and and i want the images displayed dynamically we scrolls down or left like a table view cells
can u please tell how to that...
Thanks
with the following code..when we scroll the scroll view im calling this code and able to display the images dynamically (which r only visible) but the problem is.. while scrolling with scroll bars im getting the two images..vertically and horizontally..its only happening when i scroll.. can any body help me out please..?
int tileSize;
int imgSize;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
tileSize = 255;
imgSize = 247;
}else{
tileSize = 120;
imgSize = 116;
}
CGRect visibleBounds = [songsContainer bounds];
for (UIView *tile in [songsContainer subviews]) {
CGRect scaledTileFrame =[tile frame];
if (! CGRectIntersectsRect(scaledTileFrame, visibleBounds)) {
for(UIView *view in [tile subviews])
[view removeFromSuperview];
[recycledCells addObject:tile];
[tile removeFromSuperview];
}
}
int maxRow =[songsDict count]-1; // this is the maximum possible row
int maxCol = noOfRowsInCell-1; // and the maximum possible column
int firstNeededRow = MAX(0, floorf(visibleBounds.origin.y / tileSize));
int firstNeededCol = MAX(0, floorf(visibleBounds.origin.x / tileSize));
int lastNeededRow = MIN(maxRow, floorf(CGRectGetMaxY(visibleBounds) / tileSize));
int lastNeededCol = MIN(maxCol, floorf(CGRectGetMaxX(visibleBounds) / tileSize));
NSLog(#".........MaxRow-%d,MaxCol-%d,firstNeddedRow-%d,firstNeededcol-%d,lNR-%d,lNC%d",maxRow, maxCol, firstNeededRow,firstNeededCol,lastNeededRow,lastNeededCol);
// iterate through needed rows and columns, adding any tiles that are missing
for (int row = firstNeededRow; row <= lastNeededRow; row++) {
NSMutableArray *tempArray = (NSMutableArray *)[songsDict objectAtIndex:row];
for (int col = firstNeededCol; col <= lastNeededCol ; col++) {
BOOL tileIsMissing = (firstVisibleRow > row || firstVisibleColumn > col ||
lastVisibleRow < row || lastVisibleColumn < col);
if (tileIsMissing) {
UIView *tile = (UIView *)[self dequeueReusableTile];
if (!tile) {
// the scroll view will handle setting the tile's frame, so we don't have to worry about it
tile = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
tile.backgroundColor = [UIColor clearColor];
}
//tile.image = image for row and col;
// set the tile's frame so we insert it at the correct position
CGRect frame = CGRectMake(tileSize * col, tileSize * row, imgSize, imgSize);
tile.frame = frame;
if(col<[tempArray count])
[self addContentForTile:tile:row:col];
else tile.backgroundColor = [UIColor clearColor];
[songsContainer addSubview:tile];
}
}
}
firstVisibleRow = firstNeededRow+1; firstVisibleColumn = firstNeededCol+1;
lastVisibleRow = lastNeededRow; lastVisibleColumn = lastNeededCol;
What you have to do is create your scrollview however you want. You have to decide whether you want a grid layout, or linear layout. In addition, if grid, do you want it locked to the horizontal bounds, locked to vertical, so it scrolls either vertical or horizontal, respectively.
Once you have that sorted out, then what I recommend is taking the architecture similar to how a tableview functions. That is, create individual "cells" that will hold your thumbnails. Once you have these cells, you add them as subviews of your scrollview, at certain offsets (you need to do some math on the x/y planes).
Start with a scroll view and add each image in an UIImageView as a subview to the scrollview at a certain location.
One thing to have in mind is to only hold in memory the images that are currently shown and their immediate neighbours.
I'm trying to place the icons / images horizontally in a view that is intended to show details. Maximum number of pictures per row is 6 pieces and I want to have a mechanism that manage line breaks or similar automatically. I suspect it is a custom cell that solves this?
I have tried the following code below, but the images above to add another when I reload the view. Furthermore, the cell's height is not adjusted by the view that the code returned.
-(UIImageView *)fillView{
collage = nil;
collage = [[[UIImageView alloc] initWithFrame:CGRectMake(11, 7, 0, 0)] autorelease];
int rowCounter = 0;
int colCounter = 0;
int nrOfPictures = [paymentImages count];
//max 6 images per row
while (nrOfPictures > 0) {
while (colCounter <= 6 && nrOfPictures != 0) {
UIImageView *iv = [[UIImageView alloc] initWithImage:[paymentImages objectAtIndex:nrOfPictures-1]];
CGRect frame = iv.frame;
frame.origin.x = (frame.size.width + 4) * colCounter;
frame.origin.y = (frame.size.height + 4) * rowCounter;
iv.frame = frame;
[collage addSubview:iv];
[iv release];
colCounter++;
nrOfPictures--;
if (colCounter > 6) {
colCounter = 0;
rowCounter++;
}
}
}
return collage;
}
Can someone head me in the right direction?
You might want to check out this project. http://github.com/kirbyt/KTPhotoBrowser