What is the best practice to auto resize the "ContentSize" of an UIScrollView? - uiscrollview

I have to display a few views in a UIScrollView and I want it to fit the subviews. Are there any methods that could help me? It doesn't seem hard for a UIView to resize itself according to its children but I can't find any method do such a thing. I don't know before the execution the width and the height of all my subviews as well as how many subviews I'll have.
My first idea is to create a category for the UIView or UIScrollView that will define the maximum of all subview height and width like that:
maxHeight = (subview.frame.origin.y + subview.frame.size.height)
Then when I have the width and the height that will fit all my subviews, I can perform a setContentSize on my UIScrollView. I expect that a method should already exist for that.
A solution:
#implementation UIScrollView (autoResizeContent)
- (void) resizeContentFromSubviews{
float maxWidth = 0;
float maxHeigt = 0;
float height, width;
for (UIView* v in self.subviews) {
height = (v.frame.size.height+v.frame.origin.y);
if(height > maxHeigt ){
maxHeigt = height;
}
width = (v.frame.size.width + v.frame.origin.x);
if(width > maxWidth){
maxWidth = width;
}
}
[self setContentSize:CGSizeMake(maxWidth, maxHeigt)];
}
#end

Related

ios UIScrollView - buttons exceed boundries

I'm adding buttons to my scrollview via code but when I run the app, I see all the buttons and they exceed the scrollview boundries instead of only some.
In the attached screenshot, you can see that the scrollbar is inside the boundries of the scrollview, only the buttons exceed.
why do I need self.recentFriendsScrollView.delegate = self;??
Here is my code:
//recentOpponents is an array
NSInteger xOffset = 0;
CGFloat size = 38;
CGFloat padding = 5;
self.recentFriendsScrollView.delegate = self;
for (User *user in recentOpponents) {
UIButton *tagButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
tagButton.backgroundColor = [UIColor lightGrayColor];
tagButton.frame = CGRectMake(xOffset, 8, size, size);
[self.recentFriendsScrollView addSubview:tagButton];
xOffset += size;
xOffset += padding;
}
[self.recentFriendsScrollView setContentSize:CGSizeMake(xOffset, 50.0f)];
image
Thanks
D
Your scrollview frame is not proper.
If you have set it programmatically, show the rect of frame. If it is set via sib, check your resizing flags again.

UIImageview programmilly manage for iphone5 and iphone4

I have an Issue about UIImageView Manage by XIB file for iphone5 screen Height and Iphone4 Screen Height.
I Try to Manage code for UIImageView like this
~
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
if ([UIScreen mainScreen].scale == 2.f && screenHeight == 568.0f) {
backgroundImage.autoresizingMask=UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
frameView.autoresizingMask=UIViewAutoresizingFlexibleHeight;
backgroundImage.image = [UIImage imageNamed:#"bg-568h#2x.png"];
//frameView.frame=CGRectMake(16, 0, 288, 527);
frameView.image = [UIImage imageNamed:#"setframe-568h#2x.png"];
}
else
{
backgroundImage.image = [UIImage imageNamed:#"bg#2x.png"];
frameView.image = [UIImage imageNamed:#"setframe#2x.png"];
} ;
Please suggest me about Issues, FrameView is a UIImageView which have white Image,
Please
THanks
I had the same issue and below is what I did to make it work for me.
I have images used in a couple of apps which needed to be resized for the new 4 inch display. I wrote the code below to automatically resize images as needed without specifics on the height of the view. This code assumes the height of the given image was sized in the NIB to be the full height of the given frame, like it is a background image that fills the whole view. In the NIB the UIImageView should not be set to stretch, which would do the work of stretching the image for you and distort the image since only the height changes while the width stays the same. What you need to do is adjust the height and the width by the same delta and then shift the image to the left by the same delta to center it again. This chops off a little on both sides while making it expand to the full height of the given frame.
I call it this way...
[self resizeImageView:self.backgroundImageView intoFrame:self.view.frame];
I do this in viewDidLoad normally if the image is set in the NIB. But I also have images which are downloaded at runtime and displayed that way. These images are cached with EGOCache, so I have to call the resize method either after setting the cached image into the UIImageView or after the image is downloaded and set into the UIImageView.
The code below does not specifically care what the height of the display is. It actually could work with any display size, perhaps to handle resizing images for rotation as well, thought it assumes each time the change in height is greater than the original height. To support a greater width this code would need to be adjusted to respond to that scenario as well.
- (void)resizeImageView:(UIImageView *)imageView intoFrame:(CGRect)frame {
// resizing is not needed if the height is already the same
if (frame.size.height == imageView.frame.size.height) {
return;
}
CGFloat delta = frame.size.height / imageView.frame.size.height;
CGFloat newWidth = imageView.frame.size.width * delta;
CGFloat newHeight = imageView.frame.size.height * delta;
CGSize newSize = CGSizeMake(newWidth, newHeight);
CGFloat newX = (imageView.frame.size.width - newWidth) / 2; // recenter image with broader width
CGRect imageViewFrame = imageView.frame;
imageViewFrame.size.width = newWidth;
imageViewFrame.size.height = newHeight;
imageViewFrame.origin.x = newX;
imageView.frame = imageViewFrame;
// now resize the image
assert(imageView.image != nil);
imageView.image = [self imageWithImage:imageView.image scaledToSize:newSize];
}
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

Changing size of NSTextView

I am trying to implement an NSTextView which resizes to fit its content. I am trying to make it so it has a maximum amount of lines. Meaning, that when this limit is reached, it will not grow any further and the user will be able to scroll it.
There seem to be a lot of people wanting this but I have not found a complete implementation of this.
The idea is to take the content size of the NSTextView and resize it so that it matches. I just can't figure out how to set the layout size of the NSTextView. It does not seem to be it's frame which needs to be set and which one might expect.
Can anyone please tell me how to set the frame of the NSTextView?
It turns out that it's the height of the scroll view which should be changed. Changing the height of an NSTextView can be done by overwriting the -didChangeText method like below.
- (void)didChangeText
{
NSScrollView *scrollView = (NSScrollView *) self.superview.superview;
NSRect frame = scrollView.frame;
// Calculate height
NSUInteger numberOfLines = [self numberOfLines];
NSUInteger height = frame.size.height;
if (numberOfLines <= 13)
{
height = 22 + (numberOfLines - 1) * 14;
if (height < 22)
height = 22;
}
// Update height
frame.size.height = height;
self.superview.superview.frame = frame;
}
Play around with the constants to get the result you wish for.

drawRect performance

I need to draw lots of polygons 500k to a million on the iPad. After experimenting, I can only get only get 1 fps if that. This is just an example my real code has some good sized polygons.
Here are a few question:
Why don't I have to add the Quartz Framework to my project?
If many of the polygons repeat can I leverage that with views or are they too heavy etc?
Any alternatives, QTPaint can handle this but dips into the gpu. Is there is anything like QT or ios?
Can Opengl increase 2d performance of this type?
Example drawrect:
//X Y Array of boxes
- (void)drawRect:(CGRect)rect
{
int reset = [self pan].x;
int markX = reset;
int markY = [self pan].y;
CGContextRef context = UIGraphicsGetCurrentContext();
for(int i = 0; i < 1000; i++)//1,000,000
{
for(int j = 0; j < 1000; j++)
{
CGContextMoveToPoint(context, markX, markY);
CGContextAddLineToPoint(context, markX, markY + 10);
CGContextAddLineToPoint(context, markX + 10, markY + 10);
CGContextAddLineToPoint(context, markX + 10, markY);
CGContextAddLineToPoint(context, markX, markY);
CGContextStrokePath(context);
markX+=12;
}
markY += 12;
markX = reset;
}
}
The pan just move the array of boxes around on screen with pan gesture. Any help or hints would greatly appreciated.
The key issue with your example is that it is not optimized. Whenever drawRect: is called, the device is rendering all 1,000,000 squares. Worse still, it's making 6,000,000 calls to those APIs in the loop. If you want to refresh this view at even a modest 30fps, that is 180,000,000 calls / second.
With your 'simple' example, the size of the draw area is 12,000px × 12,000px; the maximum area you can display on the iPad's display is 768×1024 (assuming full-screen portrait). Therefore, the code is wasting a lot of CPU resources drawing outside the visible area. UIKit has ways of handling this scenario with relative ease.
When managing content that is significantly larger than the visible area, you should limit drawing to only that which is visible. UIKit has a couple of ways of handing this; UIScrollView in combination with a view backed by a CATiledLayer is your best bet.
Steps:
Disclaimer: This is specifically an optimization of your example code above
Create a new View Based Application iPad project
Add a reference to the QuartzCore.framework
Create a new class, say MyLargeView, subclassed from UIView and add the following code:
:
#import <QuartzCore/QuartzCore.h>
#implementation MyLargeView
- (void)awakeFromNib {
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.tileSize = CGSizeMake(512.0f, 512.0f);
}
// Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
return [CATiledLayer class];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// only draws what is specified by the rect parameter
CGContextRef context = UIGraphicsGetCurrentContext();
// set up some constants for the objects being drawn
const CGFloat width = 10.0f; // width of rect
const CGFloat height = 10.0f; // height of rect
const CGFloat xSpace = 4.0f; // space between cells (horizontal)
const CGFloat ySpace = 4.0f; // space between cells (vertical)
const CGFloat tWidth = width + xSpace; // total width of cell
const CGFloat tHeight = height + ySpace;// total height of cell
CGFloat xStart = floorf(rect.origin.x / tWidth); // first visible cell (column)
CGFloat yStart = floorf(rect.origin.y / tHeight); // first visible cell (row)
CGFloat xCells = rect.size.width / tWidth + 1; // number of horizontal visible cells
CGFloat yCells = rect.size.height / tHeight + 1; // number of vertical visible cells
for(int x = xStart; x < (xStart + xCells); x++) {
for(int y = yStart; y < (yStart + yCells); y++) {
CGFloat xpos = x*tWidth;
CGFloat ypos = y*tHeight;
CGContextMoveToPoint(context, xpos, ypos);
CGContextAddLineToPoint(context, xpos, ypos + height);
CGContextAddLineToPoint(context, xpos + width, ypos + height);
CGContextAddLineToPoint(context, xpos + width, ypos);
CGContextAddLineToPoint(context, xpos, ypos);
CGContextStrokePath(context);
}
}
}
#end
Edit the view controller nib and add a UIScrollView to the view
Add a UIView to the UIScrollView and make sure it fills the UIScrollView
Change the class to MyLargeView
Set frame size of MyLargeView to 12,000×12,000
Finally, open up the view controller .m file and add the following override:
:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView *scrollView = [self.view.subviews objectAtIndex:0];
scrollView.contentSize = CGSizeMake(12000, 12000);
}
If you look at the drawRect: call, it is only drawing into the area specified by the rect parameter, which will correspond to the tile size (512×512) for the CATiledLayer we configured in the awakeFromNib method. This will scale to a 1,000,000×1,000,000 pixel canvas.
Alternatives to look at are the ScrollViewSuite example, specifically 3_Tiling.
OpenGL is GPU hardware accelerated on iOS devices. Core Graphics drawing is not, and can be many many times slower when dealing with a large number of small graphics primitives (lines).
For lots of small squares, just writing them into a bitmap in C code is faster than Core Graphics line drawing. Then just draw the bitmap to the view once when done. But Open GL would be even faster.
point 4. OpenGL should do that fine. Check if you could reuse those objects and whether you could move some of the logic to GLSL code.
OpenGL performance optimization (in context of WebGL but most of it should apply): http://www.youtube.com/watch?v=rfQ8rKGTVlg
I don't know the details of iOS history so this may not have been an option when the question was first posted. However, I wanted to call out CAShapeLayer as a simple option when dealing with path performance problems. "iOS Core Animation: Advanced Techniques" (find it on Google Books) says CAShapeLayer "uses hardware-accelerated drawing" which I'm taking to mean that it's a GPU-based implementation. The same book has a good usage example in chapter 6, which boils down to this:
Create a CAShapeLayer
Configure its lineWidth, fillColor, strokeColor, etc.
Add the layer as a sublayer of your view's containerView.layer
To draw a path, just set it to the layer's "path" property
This made a gigantic performance difference in my app, as measured by Instruments. If your performance problem is path-based, don't wade into OpenGL before you've tried CAShapeLayer.
I encountered the same problem. After endless searching on google,CAShapeLayer saved me finally! Here is the detail steps you need to do:
Create a view with CAShapeLayer as it's layer type by override UIView's + (Class)layerClass method
Configure the layer's lineWidth, fillColor, strokeColor, etc.
Create an UIBezierPath instance
To draw a path,use UIBezierPath instance to add lines,curve,or acr etc, after you finished drawing, just set bezierPath.CGPath to the
layer's "path" property
Here is a simple demo to draw a simple curve when you touch the demo view:
//Simple ShapelayerView.m
-(instancetype)init {
self = [super init];
if (self) {
_bezierPath = [UIBezierPath bezierPath];
CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
shapeLayer.lineWidth = 5;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.strokeColor = [UIColor yellowColor].CGColor;
shapeLayer.fillColor = [UIColor blueColor].CGColor;
}
return self;
}
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (void) customDrawShape {
CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
[_bezierPath removeAllPoints];
[_bezierPath moveToPoint:CGPointMake(10, 10)];
[_bezierPath addQuadCurveToPoint:CGPointMake(2, 2) controlPoint:CGPointMake(50, 50)];
shapeLayer.path = _bezierPath.CGPath;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesBegan:touches withEvent:event];
[self customDrawShape];
}

Autoscroll problen

1.Create a cocoa application (not document-based)
2.Create a new class "StretchView"(subclass NSView)
3.Open the Interface builder and drag a "Scroll view" to the main window
4.Choose the "Scroll view" and set the class "StretchView" (in class identity window)
The size of the contentview is 500*500 and the size of the strechview is also 500*500
(horizontal Scroll is enabled).
Then I start to draw some numbers(1,2,3,4......) horizontally one after the other.
When the number is out of ranger(the x pos is larger than 500) I increase the width
of the StretchView. (Everything works fine up till this point)
Then I tried to make the horizontal scroller to automatically scroll to the end so
that everytime I increase the width of the StretchView the last number coulde be
seen.
Here's the code:
//The timer is called every sec
-(void)myTimerAction:(NSTimer *) timer
{
NSLog(#"myTimerAction");
//......
int i = _myArray.count;
NSRect rect = [self frame];
int width = rect.size.width;
//The width between two number is 10
//When the x pos of current num is bigger then the scroll's width
if((i * 10) > width) {
//reset the width
width = i * 10;
[self setFrameSize:CGSizeMake(width, rect.size.height)];
//How to make it autoscroll???
//...............................
}
//......
[self setNeedsDisplay:YES];
}
Try this:
NSView *contentView = [[self enclosingScrollView] contentView];
CGFloat newXPosition = width - NSWidth([contentView bounds]);
if (newXPosition > 0.0) [self scrollPoint:NSMakePoint(newXPosition, 0.0)];
contentView is the clipping view in the enclosing scroll view. You want to scroll your current view to the x point in your view such that this x point + the clipping view width give the last clipping view width points of your view.

Resources