I am creating (or attempting to) a custom UISlider look, still horizontal but much taller. I have two problems.
1.This is the code I'm using to get the images onto the slider.
UIImage *minImage = [UIImage imageNamed:#"sliderMin.png"];
UIImage *maxImage = [UIImage imageNamed:#"sliderMax.png"];
UIImage *thumbImage = [UIImage imageNamed:#"sliderThumb.png"];
minImage = [minImage stretchableImageWithLeftCapWidth:33.0 topCapHeight:0.0];
maxImage = [maxImage stretchableImageWithLeftCapWidth:33.0 topCapHeight:0.0];
// thumbImage = [minImage stretchableImageWithLeftCapWidth:10.0 topCapHeight:0.0];
[_contrastSlider setMinimumTrackImage:minImage forState:UIControlStateNormal];
[_contrastSlider setMaximumTrackImage:maxImage forState:UIControlStateNormal];
[_contrastSlider setThumbImage:thumbImage forState:UIControlStateNormal];
minImage = nil;
maxImage = nil;
thumbImage = nil;
[_contrastSlider setMinimumValue:0.0];
[_contrastSlider setMaximumValue:100.00];
[_contrastSlider setValue:25.0];
I have three images, min, max and thumb. These are the images and how they look on the device, which isn't correct.
I can't post images yet, so here is a link to MYSlider.jpeg which shows the components.
The slider should be cream to the right of the thumb, pink to the left. But this isn't happening.
That's problem 1. Problem 2 is the thumb size default is too small. I'm using this :UISlider Height Hack to try to increase the height of the thumb, but sadly it's making it wider not taller. So I am asking for help to make the area taller, not wider, and wondering why my pink image is being ignored?
Thank you for any help.
If it helps, here is my UISlider subclass .m:
-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent*)event {
CGRect bounds = self.bounds;
bounds = CGRectInset(bounds, -10, -8);
return CGRectContainsPoint(bounds, point);
}
-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGRect bounds = self.bounds;
float thumbPercent = (self.value - self.minimumValue) / (self.maximumValue - self.minimumValue);
float thumbPos = THUMB_SIZE + (thumbPercent * (bounds.size.width - (2 * THUMB_SIZE)));
CGPoint touchPoint = [touch locationInView:self];
return (touchPoint.x >= (thumbPos - EFFECTIVE_THUMB_SIZE) && touchPoint.x <= (thumbPos + EFFECTIVE_THUMB_SIZE));
}
Thank you.
It turns out setting my slider values in the views did load method was causing problems. I set them in Interface Builder and that was fixed. Once set in IB I can tweak them in code if needs be with no problems.
As for my other problem, I'm wondering if this is because I'm not testing on an actual device, so I'm going to pause that question until I get it on the device in a few weeks.
Related
I have a UITextField with the text right-aligned.
I wanted to change the color of the placeholder text, so I use - (void)drawPlaceholderInRect:(CGRect)rect method. It works great BUT the placeholder text is left-aligned now (the text remains right-aligned). I guess I can add some code to override it but I didn't find which one. Thanks in advance !
- (void)drawPlaceholderInRect:(CGRect)rect
{
[[UIColor redColor] setFill];
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Medium" size:18];
[[self placeholder] drawInRect:rect withFont:font];
}
Here is the code snippet based on Michael solution. You should create subclass of text field and add the below method. Below method basically changes x-position and width of place holder bounds.
- (CGRect)placeholderRectForBounds:(CGRect)bounds{
CGRect newbounds = bounds;
CGSize size = [[self placeholder] sizeWithAttributes:
#{NSFontAttributeName: self.font}];
int width = bounds.size.width - size.width;
newbounds.origin.x = width ;
newbounds.size.width = size.width;
return newbounds;
}
You've discovered that "drawInRect" is automagically drawing from the left edge going right.
What you need to do is adjust the "rect" passed to "drawInRect" to have left edge that allows the right edge of the drawn text to touch the right edge of your UITextField rect.
To do this, I'd recommend using this method: NSString's [self placeholder] sizeWithFont: constrainedToSize:] (assuming [self placeholder] is a NSString) which will give you the true width of the string. Then subtract the width from the right edge of the text field box and you have the left edge where you need to start your drawing from.
I enhanced #Saikiran's snippet a little, this works for me:
- (CGRect)placeholderRectForBounds:(CGRect)bounds
{
return self.editing ? ({CGRect bounds_ = [super placeholderRectForBounds:bounds];
bounds_.origin.x = bounds_.size.width
- ceilf(self.attributedPlaceholder.size.width)
+ self.inset.x;
bounds_.origin.y = .5f * (.5f * bounds_.size.height
- ceilf(self.attributedPlaceholder.size.height));
bounds_.size.width = ceilf(self.attributedPlaceholder.size.width);
bounds_.size.height = ceilf(self.attributedPlaceholder.size.height);
bounds_;
}) : [super placeholderRectForBounds:bounds];
}
I want to implement iPhone Photo App (default iPhone app). I faced difficulties, when I want to load big image (2500 * 3700). When I want to scroll from one image to another, I see something like stuttering. To display images I use ImageScrollView from apple site It has displaying method: ImageScrollView.m
- (void)displayImage:(UIImage *)image
{
// clear the previous imageView
[imageView removeFromSuperview];
[imageView release];
imageView = nil;
// reset our zoomScale to 1.0 before doing any further calculations
self.zoomScale = 1.0;
self.imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
[self addSubview:imageView];
self.contentSize = [image size];
[self setMaxMinZoomScalesForCurrentBounds];
self.zoomScale = self.minimumZoomScale;
}
- (void)setMaxMinZoomScalesForCurrentBounds
{
CGSize boundsSize = self.bounds.size;
CGSize imageSize = imageView.bounds.size;
// calculate min/max zoomscale
CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise
CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise
CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible
// on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the
// maximum zoom scale to 0.5.
CGFloat maxScale = 1.0 / [[UIScreen mainScreen] scale];
// don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.)
if (minScale > maxScale) {
minScale = maxScale;
}
self.maximumZoomScale = maxScale;
self.minimumZoomScale = minScale;
}
For my app I have 600*600 images, I show them first. When user scrolls to next image he sees only 600*600 image. Then in background I load 3600 * 3600 image
[operationQueue addOperationWithBlock:^{
UIImage *image = [self getProperBIGImage];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
ImageScrollView *scroll = [self getCurrentScroll];
[scroll displayImage:newImage];
}];
}];
I see, that when the dimensions of image are 3600 * 3600 and I want to display image in 640 * 960 screen, iPhone waste 1 second of main queue time to scale the image, and that's why I can't scroll to next image during this 1 second.
I want to scale image, because I need user to be able to zoom this image. I tried to use this approach, but this didn't help.
I see some possible solutions:
1) to provide scaling of image in UIImageView in background (but I know, that UI should be changed only in main thread)
2) to use - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollViewCalled to show only 600 * 600 image at the beginning and then load big image, when user tries to zoom (but I tried this, and I will loose 1 second, when I try to init UIImageView with bigImage and then return this UIImageView; And I can't even implement it, because I see bad scroll view, where scrolling behavior is wrong (difficault to explain), when I try to return different view for different scales)
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollViewCalled
{
if (!zooming)
{
ImageScrollView *scroll = (ImageScrollView *)scrollViewCalled;
UIImageView *imageView = (UIImageView *)[scroll imageView];
return imageView;
}
else
{
UIImageView *bigImageView = [self getBigImageView];
return bigImageView;
}
}
Unfortunately, that image is too large for the iPhone to handle. I know on the iPad, the limit is roughly the size of the device screen. Any bigger than that and you'll have to use a CATiledLayer.
I would take a look at the WWDC UIScrollView presentations over the last three year, starting with 2010. In that, they discuss ways to handle large images on the iPhone. The sample code will also get you on your way.
Good luck!
in iOS, does any UIImage support stretchableImageWithLeftCapWidth: , does that mean autoresize the uimmage?
First, this is deprecated, replaced by the more powerful resizableImageWithCapInsets:. However, that is only supported by iOS 5.0 and above.
stretchableImageWithLeftCapWidth:topCapHeight: does not resize the image you call it on. It returns a new UIImage. All UIImages may be drawn at different sizes, but a capped image responds to resizing by drawing its caps at the corners, and then filling the remaining space.
When is this useful? When we want to make buttons out of an image, as in this tutorial for the iOS 5 version.
The following code is a UIView drawRect method which illustrates the difference between a regular UIImage and a stretchable image with caps. The image used for stretch.png came from http://commons.wikimedia.org/wiki/Main_Page.
- (void) drawRect:(CGRect)rect;
{
CGRect bounds = self.bounds;
UIImage *sourceImage = [UIImage imageNamed:#"stretch.png"];
// Cap sizes should be carefully chosen for an appropriate part of the image.
UIImage *cappedImage = [sourceImage stretchableImageWithLeftCapWidth:64 topCapHeight:71];
CGRect leftHalf = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width/2, bounds.size.height);
CGRect rightHalf = CGRectMake(bounds.origin.x+bounds.size.width/2, bounds.origin.y, bounds.size.width/2, bounds.size.height);
[sourceImage drawInRect:leftHalf];
[cappedImage drawInRect:rightHalf];
UIFont *font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
[#"Stretching a standard UIImage" drawInRect:leftHalf withFont:font];
[#"Stretching a capped UIImage" drawInRect:rightHalf withFont:font];
}
Output:
I have written a category method for maintaining compatibility
- (UIImage *) resizableImageWithSize:(CGSize)size
{
if( [self respondsToSelector:#selector(resizableImageWithCapInsets:)] )
{
return [self resizableImageWithCapInsets:UIEdgeInsetsMake(size.height, size.width, size.height, size.width)];
} else {
return [self stretchableImageWithLeftCapWidth:size.width topCapHeight:size.height];
}
}
just put that into your UIImage category you already have ( or make a new one )
this only supports the old way stretchable resizing, if you need more complex stretchable image resizing you can only do that on iOS 5 using the resizableImageWithCapInsets: directly
I'm running into problems when dealing with a large amount of UIButtons in my interface. I was wondering if anyone had first hand experience with this and how they did it?
When dealing with 30-80 buttons most simple, a couple of complex do you just use UIButton or do something different like drawRect, respond to touch events and get the coordinates of the touch event?
Best example is a calendar, similar to that of Apples Calendar App. Would you just draw most of the days using drawRect and then when you click a button replace it with an image or just use UIButtons? It's not so much the memory footprint or creating the buttons, just strange things are happening with them sometimes (previous question about it) and having performance issues animating them.
Thanks for any help.
If "strange things are happening" with your buttons, you need to get to the bottom of why. Switching architectures just to avoid a problem that you don't understand (and might crop up again) doesn't sound like a good idea.
-drawRect: works by drawing to a bitmap-backed context. This happens when -displayIfNeeded is called after -setNeedsDisplay (or doing something else that implicitly sets the needsDisplay flag, like resizing a view with contentMode = UIContentModeRedraw). The bitmap-backed context is then composited to screen.
Buttons work by putting the different components (background image, foreground image, text) in different layers. The text is drawn when it changes and composited to the screen; the images are just composited directly to the screen.
The "best" way to do things is usually a combination of the two. For example, you might draw text and a background image in -drawRect: so the different layers didn't need to be composited at render time (you get an additional speedup if your view is "opaque"). You probably want to avoid full-screen animations via drawRect: (and it won't integrate so well with CoreAnimation), since drawing tends to be more expensive than compositing.
But first, I'd find out what's going wrong with UIButton. There's little point worrying about how you could make things faster until you actually find out what the slow bits are. Write code so that it is easy to maintain. UIButton is not that expensive and -drawRect: is not that bad (presumably it's even better if you use -setNeedsDisplayInRect: for a smallish rect, but then you need to calculate the rect...), but if you want a button, use UIButton.
Instead of using 30-80 UIButtons I will prefer using images (if possible, a single image or as small number as possible) and compare the touch location.
And if I must create buttons, then obviously will not create 30-80 variables for them. I will set and get view tag to determine which one is tapped.
If this is all stuff you are animating then you could create a bunch of CALayers with their contents set to a CGImage. You would have to compare the touch location to identify the layer. CALayers have a useful style property that is an NSDictionary you can store meta-data in.
I just use the UIButtons unless there happens to be a specific performance issue that crops up. If they have similar functionality, however, such as a keyboard, I map them all to one IBAction and differentiate the behavior based on the sender.
What specific performance and animation issues are you running into?
I recently ran across this problem myself when developing a game for the iPhone. I was using UIButtons to hold game tiles, then stylized them with transparent images, background colors and text.
It all worked well for a small number of tiles. Once we got to about 50, however, the performance dropped significantly. After scouring Google I discovered that others had experienced the same problem. It seems the iPhone struggles with lots of transparent buttons onscreen at once. Not sure if it's a bug in the UIButton code or just a limitation of the graphics hardware on the device, but either way, it's beyond your control as a programmer.
My solution was to draw the board by hand using Core Graphics. It seemed daunting at first, but in reality it was pretty easy. I just placed one big UIImageView on my ViewController in Interface Builder, made it an IBOutlet so I could alter it from Objective-C, then constructed the image with Core Graphics.
Since a UIImageView doesn't handle taps, I used the touchesBegan method of my UIViewController, and then triangulated the x/y coordinates of the touch to the precise tile on my game board.
The board now renders in less than a tenth of a second. Bingo!
If you need sample code, just let me know.
UPDATE: Here's a simplified version of the code I'm using. Should be enough for you to get the gist.
// CoreGraphicsTestViewController.h
// CoreGraphicsTest
#import <UIKit/UIKit.h>
#interface CoreGraphicsTestViewController : UIViewController {
UIImageView *testImageView;
}
#property (retain, nonatomic) IBOutlet UIImageView *testImageView;
-(void) drawTile: (CGContextRef) ctx row: (int) rowNum col: (int) colNum isPressed: (BOOL) tilePressed;
#end
... and the .m file ...
// CoreGraphicsTestViewController.m
// CoreGraphicsTest
#import "CoreGraphicsTestViewController.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>
#implementation CoreGraphicsTestViewController
#synthesize testImageView;
int iTileSize;
int iBoardSize;
- (void)viewDidLoad {
int iRow;
int iCol;
iTileSize = 75;
iBoardSize = 3;
[testImageView setBounds: CGRectMake(0, 0, iBoardSize * iTileSize, iBoardSize * iTileSize)];
CGRect rect = CGRectMake(0.0f, 0.0f, testImageView.bounds.size.width, testImageView.bounds.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
for (iRow = 0; iRow < iBoardSize; iRow++) {
for (iCol = 0; iCol < iBoardSize; iCol++) {
[self drawTile: context row: iRow col: iCol color: isPressed: NO];
}
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
[testImageView setImage: image];
UIGraphicsEndImageContext();
[super viewDidLoad];
}
- (void)dealloc {
[testImageView release];
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView: testImageView];
if ((location.x >= 0) && (location.y >= 0) && (location.x <= testImageView.bounds.size.width) && (location.y <= testImageView.bounds.size.height)) {
UIImage *theIMG = testImageView.image;
CGRect rect = CGRectMake(0.0f, 0.0f, testImageView.bounds.size.width, testImageView.bounds.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[theIMG drawInRect: rect];
iRow = location.y / iTileSize;
iCol = location.x / iTileSize;
[self drawTile: context row: iRow col: iCol color: isPressed: YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
[testImageView setImage: image];
UIGraphicsEndImageContext();
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIImage *theIMG = testImageView.image;
CGRect rect = CGRectMake(0.0f, 0.0f, testImageView.bounds.size.width, testImageView.bounds.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[theIMG drawInRect: rect];
[self drawTile: context row: iRow col: iCol isPressed: NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
[testImageView setImage: image];
UIGraphicsEndImageContext();
}
-(void) drawTile: (CGContextRef) ctx row: (int) rowNum col: (int) colNum isPressed: (BOOL) tilePressed {
CGRect rrect = CGRectMake((colNum * iTileSize), (rowNum * iTileSize), iTileSize, iTileSize);
CGContextClearRect(ctx, rrect);
if (tilePressed) {
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
} else {
CGContextSetFillColorWithColor(ctx, [[UIColor greenColor] CGColor]);
}
UIImage *theImage = [UIImage imageNamed:#"tile.png"];
[theImage drawInRect: rrect];
}
I have a grouped UITableView that contains several cells (just standard UITableViewCells), all of which are of UITableViewCellStyleSubtitle style. Bueno. However, when I insert images into them (using the provided imageView property), the corners on the left side become square.
Example Image http://files.lithiumcube.com/tableView.png
The code being used to assign the values into the cell is:
cell.textLabel.text = currentArticle.descriptorAndTypeAndDifferentiator;
cell.detailTextLabel.text = currentArticle.stateAndDaysWorn;
cell.imageView.image = currentArticle.picture;
and currentArticle.picture is a UIImage (also the pictures, as you can see, display just fine with the exception of the square corners).
It displays the same on my iPhone 3G, in the iPhone 4 simulator and in the iPad simulator.
What I'm going for is something similar to the UITableViewCells that Apple uses in its iTunes app.
Any ideas about what I'm doing wrong?
Thanks,
-Aaron
cell.imageView.layer.cornerRadius = 16; // 16 is just a guess
cell.imageView.clipsToBounds = YES;
This will round the UIImageView so it does not draw over the cell. It will also round all the corners of all your images, but that may be OK.
Otherwise, you will have to add your own image view that will just round the one corner. You can do that by setting up a clip region in drawRect: before calling super. Or just add your own image view that is not so close to the left edge.
You can add a category on UIImage and include this method:
// Return the image, but with rounded corners. Useful for masking an image
// being used in a UITableView which has grouped style
- (UIImage *)imageWithRoundedCorners:(UIRectCorner)corners radius:(CGFloat)radius {
// We need to create a CGPath to set a clipping context
CGRect aRect = CGRectMake(0.f, 0.f, self.size.width, self.size.height);
CGPathRef clippingPath = [UIBezierPath bezierPathWithRoundedRect:aRect byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)].CGPath;
// Begin drawing
// Start a context with a scale of 0.0 uses the current device scale so that this doesn't unnecessarily drop resolution on a retina display.
// Use `UIGraphicsBeginImageContextWithOptions(aRect.size)` instead for pre-iOS 4 compatibility.
UIGraphicsBeginImageContextWithOptions(aRect.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, clippingPath);
CGContextClip(context);
[self drawInRect:aRect];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
Then when you're configuring your cells, in the table view controller, call something like:
if ( *SINGLE_ROW* ) {
// We need to clip to both corners
cell.imageView.image = [image imageWithRoundedCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft) radius:radius];
} else if (indexPath.row == 0) {
cell.imageView.image = [image imageWithRoundedCorners:UIRectCornerTopLeft radius:radius];
} else if (indexPath.row == *NUMBER_OF_ITEMS* - 1) {
cell.imageView.image = [image imageWithRoundedCorners:UIRectCornerBottomLeft radius:radius];
} else {
cell.imageView.image = image;
}
but replace the SINGLE_ROW etc with real logic to determine whether you've got a single row in a section, or it's the last row. One thing to note here, is that I've found (experimentally) that the radius for a group style table is 12, which works perfectly in the simulator, but not on an iPhone. I've not been able to test it on a non-retina device. A radius of 30 looks good on the iPhone 4 (so I'm wondering if this is an image scale thing, as the images I'm using are from the AddressBook, so don't have an implied scale factor). Therefore, I've got some code before this that modifies the radius...
CGFloat radius = GroupStyleTableCellCornerRadius;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2){
// iPhone 4
radius = GroupStyleTableCellCornerRadiusForRetina;
}
hope that helps.