I'm making some CGRect Utils class and function to help us manipulating CGRect and View's frame.
I'm facing a very strange problem. I have an image, that I can move by pressing some button.
- (IBAction)heightPlusTen:(id)sender {
CGRectAddHeightToView(myView, 10);
}
CGRectAddHeightToView is just a #define and it can be replace by:
- (IBAction)heightPlusTen:(id)sender {
myView.frame = CGRectMake( myView.frame.origin.x,
myView.frame.origin.y,
myView.frame.size.width,
myView.frame.size.height+10 );
}
So far the image is moving. Everything looks fine...
Until I run this code:
- (IBAction)updatePosition:(id)sender {
xLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.origin.x];
yLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.origin.y];
wLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.size.width];
hLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.size.height];
}
It's suppose to display the x,y,width and height on the screen. But instead it move back the image to it almost initial position.
I know lot of code is missing so checkout CGRect Utils on github.
It's autolayout that's causing the problem.. Now you can just delete auto-layout all together, or you can keep it (along with its many benefits) but also insantiate the labels as soon as the xib loads.. like so:
// ViewController.m
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
xLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.origin.x];
yLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.origin.y];
wLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.size.width];
hLabel.text = [NSString stringWithFormat:#"%.0f", duck.frame.size.height];
}
that way autolayout won't have to readjust anything when you update the labels in the future.
You really should consider using ARC in this day and age.
As for your question: autolayout is causing this frame reset when you update the labels.
If you disable it on ViewController.xib then your code works "as expected".
A very good tutorial on autolayout and how to use it can be found here: Beginning Auto Layout in iOS 6
Autolayout is a great tool but can sometimes cause serious headaches.
Related
I'm trying to add syntax highlighting to my program.I'm using this code to add highlights:
-(void)highlightWord:(NSString *)word: (UIColor *)color {
int amount = textDisplay.text.length;
NSString *newString = textDisplay.text;
NSUInteger count = 0, length = amount;
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [textDisplay.text rangeOfString: word options:NSLiteralSearch range:range];
if(range.location != NSNotFound)
{
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
while (count != 0) {
count--;
NSRange highlight = [newString rangeOfString:word];
newString = [textDisplay.text stringByReplacingCharactersInRange:highlight withString:#" "];
UILabel *view1 = [[UILabel alloc] initWithFrame:[self frameOfTextRange:highlight inTextView:textDisplay]];
view1.text = word;
view1.textColor = color;
view1.font = [UIFont fontWithName:#"System" size: 14.0];
view1.backgroundColor = [UIColor clearColor];
view1.textAlignment = UITextAlignmentLeft;
[textDisplay addSubview:view1];
}
}
- (IBAction)highlighString:(id)sender {
for (UIView *subview in [textDisplay subviews]){
if ([subview isKindOfClass:[UILabel class]]){
[subview removeFromSuperview];
}
}
[self highlightWord:#"test" :[UIColor blueColor]];
[self highlightWord:#"this" :[UIColor redColor]];
[self highlightWord:#"is" :[UIColor grayColor]];
[self highlightWord:#"a" :[UIColor greenColor]];
}
#end
But this seems to cause a weird results:
The desired result would be that it overlays to colored label seamlessly.
Mixing UILabel and UITextView is unlikely to give you easy copy and paste. It's an interesting idea, but I'd be surprised if it worked seamlessly. The usual Apple solution for this problem in all non-beta versions of iOS is to use a UIWebView, horrible as that is. Cocoanetics has one called DTRichTextEditor which I haven't reviewed, but based on the quality of their work on DTCoreText (which I'm very impressed with), I have high hopes for it for iOS 5. Betas are under NDA, but you may be interested in watching Session 220 from WWDC 2012 for more future-looking information.
Your specific bug is that you're adding the labels as subviews to the text view. You should put them on top of the text view as peers. Putting them inside the textview makes you subject the text view's drawing system, which is biting you. Of course when you put them on top of the text view, that's going to make it hard to handle copy and paste. You'll need to pass the touches through somehow, and then draw the selection correctly. See the previous paragraph.
If a fully editable rich-text view were easy to hack up with some labels, then you'd find several implementations on GitHub. Unfortunately it's actually pretty tough to do well. I bow to Cocoanetics for taking it on; I gave up pretty quickly. If you can simplify your problem to a non-general text view, then it may be possible this way, but I'd recommend investigating DTRichTextEditor or a UIWebView. See Session 511 "Rich Text Editing in Safari on iOS" from WWDC 2011.
I am currently trying to nest a UIWebView (among other elements) into a custom UITableViewCell subclass.
I need to dynamically determine the size of the cell (using heightForRowAtIndexPath) but for some reason when I do it like so, the height always gets printed as 0:
UIWebView* wv = [[[UIWebView alloc] init] autorelease];
[wv loadHTMLString:body baseURL:nil];
[wv setNeedsLayout]; //I don't think these are necessary but I tried anyway.
[wv setNeedsDisplay];
CGSize wvSize = [wv sizeThatFits:webViewBounds];
NSLog(#"WVHEIGHT %f", wvSize.height); //THIS IS WHERE IT PRINTS.
CGFloat retVal = 10.0f;
retVal += 50 > wvSize.height ? 50 : wvSize.height;
retVal += 2 + 15 + 10;
return retVal;
Now, if I do that exact same calculation using the cell.webView that I have access to in cellForRowAtIndexPath, it returns a non-zero value. However, I'd really like to avoid loading an entire cell just to figure out how tall it should be...
Additionally, UIWebView is very slow at rendering text (and it is unfortunately not negotiable that I use UIWebView), so I was wondering if there's a way to tell it to display the typical Apple "rotating gear" activity icon until it has its text fully loaded and ready to render.
I would recommend hiding each UIWebView until it is done loading, and showing a loading indicator instead. I would then make a dictionary with keys that match the tags, and for each entry in the dictionary, I would store the size of the UIWebView.
Make your controller conform to the UIWebViewDelegate protocol, and use the following code:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *output = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('this').offsetHeight;"];
[self.webViewHeights setValue:[output floatValue] forKey:[NSString stringWithFormat:#"%f", webView.tag]];
}
After that, add code to hide your loading view and display your UIWebView, then call a table reload. In your cell height method, just check the correct array element for your UIWebView height.
Edit:
It seems I left out the important part!
OK, so, to create non-visible UIWebViews, you just need to create them, but not actually add them to a view. For example, you could do something like this in your viewDidLoad method:
UIWebView *newWebView = [[UIWebView alloc] initWithFrame:newFrame];
[newWebView loadHTMLString:body baseURL:nil];
newWebView.tag = 1;
[self.webViewDictionary setObject:newWebView forKey:#"1"];
Then in your table code, say something like
NSString *tagString = [NSString stringWithFormat:#"%d", indexPath.row];
UIWebView *curWebView = [self.webViewDictionary objectForKey:tagString];
if (curWebView.loading == NO) {
CGRect newFrame = curWebView.frame;
newFrame.size.height = [self.webViewHeights objectForKey:tagString];
curWebView.frame = newFrame;
[cell addSubview:curWebView];
} else {
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorStyleGray];
[cell addSubview:activityIndicator];
}
I don't think sizeThatFits: is what you want to use.
sizeThatFits:
Asks the view to calculate and return
the size that best fits its subviews.
Your webview does not have any subviews, only HTML.
UIWebView has no init method of its own, so you probably should use initWithFrame: (from the UIView superclass) to set the size directly. You can set it to the size you want, which is the size of the tableView cell in this case.
I have been through every single thing allocated in my code and given it a tag of -1, except from the Icons, which use up the tag system. So here's my code:
NSLog(#" 1: %#", (Icon *)[self viewWithTag:index]);
Icon *icon = (Icon *)[self viewWithTag:index];
CGRect frame = icon.frame;
[icon removeFromSuperview];
icon = nil;
Icon *icon2 = [[Icon alloc] init];
[icon2 makeIconStandardWithTag:(int)index];
icon2.frame = frame;
[self addSubview:icon2];
NSLog(#" 2: %#", (Icon *)[self viewWithTag:index]);
NSLog 1 returns the object to be an icon. NSLog 2 returns the object to be a UIImageView, despite me searching my code thoroughly for every UIImageView and giving it a -1 tag. Through moving NSLog 2 around, i've discovered that the line [icon removeFromSuperview]; is the problem here. If that line isn't included, it doesn't happen. But obviously i need to remove it from superview and .alpha = 0is too much of a patch-over fix.
What value are you using for the tag? Sometimes I have troubles if I use a value that's too low (I assume because UIKit is using that tag value).
Try setting index to some random large number.
Also, why not just use an instance variable to refer to the Icon? Then you won't have to mess around with identifying the icon by its tag.
I've read a lot of UIScrollView with UIImageView threads here or other googled pages. But I still cannot get the problem I'm confronting. I'm having a cold right now. Hope I can still make it clear, lol. Here is the problem:
I'm building one app which mainly uses UIScrollView to display a few images. Here the amount counts, not the size, which is averagely 100KB(I even converted PNG to jpg, not sure whether it helps or not). With no more than 10 images, my app crashes with memory warning. This is the first time I encounter memory issue, which surprised me as the compiled app is less than 10MB.
At the very beginning, I load all the images on launch, looping all names of image files and do
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imgName]];
[scrollview addSubview:imageView];
[imageView release];
If I'm right, I think after launch, all the images are in memory, right? But the funny thing here is, the app could launch without any problem(at most a level 1 memory warning). After I scroll a few images, it crashed. I checked leaks for sure and also allocations. No leak and allocation almost had no change during scrolling.
So, is there something special done by imageNamed rather than cache?
And then, yes, I turned to lazy load.
For fear of checking page and loading images on demand might jerk the scrolling(which was proved true), I used a thread which runs a loop to check offset of the scroll view and load/unload images.
I subclassed UIImageView by remembering the image name. It also contains loadImage and unloadImage which will be executed on that thread.
- (void)loadImage {
/if ([self.subviews count] == 0) {
UIImageView iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.imageName]];
[self performSelectorOnMainThread:#selector(renderImage:) withObject:iv waitUntilDone:NO];
//[self addSubview:iv];
[iv release];
}*/
if (self.image == nil) {
//UIImage *img = [UIImage imageNamed:self.imageName];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[self.imageName stringByDeletingPathExtension] ofType:[self.imageName pathExtension]]];
// image must be set on main thread as UI rendering is main thread's responsibility
[self performSelectorOnMainThread:#selector(renderImage:) withObject:img waitUntilDone:NO];
[img release];
}
}
// render image on main thread
- (void)renderImage:(UIImage*)iv {
//[self addSubview:iv];
self.image = iv;
}
(void)unloadImage {
self.image = nil;
//[(UIView*)[self.subviews lastObject] removeFromSuperview];
}
You can see the commented code that I've played with.
In unloadImage, if I write [self.image release], then I get EXC_BAD_ACCESS, which is unexpected, as I think alloc and release are matched here.
The app still crashes with no leak. The initWithContentsOfFile version even crashed earlier than imageNamed version, and made the scrolling not that smooth.
I run the app on device. By checking allocations, I found imageNamed version used much less memory than initWithContentsOfFile version, though they both crash. Instruments also showed that the allocated images were 2,3 or 4, which indicated the lazy load did do his job.
I checked PhotoScroller of WWDC2010, but I don't think it solvs my problem. There is no zooming or huge picture involved.
Anybody helps! Thank you in advance.
The crash log says nothing. The app crashes mostly after memory warning level = 2. And if run on simulator, there will be no problem.
It doesn't matter which format do you use for your images. They're converted to bitmaps when you display them.
I'd suggest to use the technique similar to that one which is used by UITableView (hide the image and free the memory it uses when it disappears from the screen and instantiate the image only when you need to show it).
As an alternate way – if you need to show these images in a grid – you might take a look to a CATiledLayer.
Anyhow, loading all the images to the memory is not the best idea :)
You can load all the images to an array. And you can design a view having one image view and try the below code:
array name: examplearray and view name :exampleview
-(void)updateImagesInScrollView
{
int cnt = [examplearray count];
for(int j=0; j< cnt; ++j)
{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"exampleview"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
exampleview * rview= (exampleview *)myView;
/* you can get your iamge from the array and set the image*/
rview.imageview.image = yourimage;
/*adding the view to your scrollview*/
[self.ScrollView addSubview:rview];
}
/* you need to set the content size of the scroll view */
self.ScrollView.contentSize = CGSizeMake(X, self.mHorizontalScrollView.contentSize.height);
}
Im developing an app for an iPhone and I found that the following code is causing the memory allocation to increment.
-(UIImage *)createRecipeCardImage:(Process *)objectTBD atIndex:(int)indx
{
[objectTBD retain];
// bringing the image for the background
UIImage *rCard = [UIImage imageNamed:#"card_bg.png"];
CGRect frame = CGRectMake(00.0f, 80.0f, 330.0f, 330.0f);
// creating he UIImage view to contain the recipe's data
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = rCard;
[rCard release];
imageView.userInteractionEnabled = YES;
float titleLabelWidth = 150.0;
float leftGutter = 5.0;
float titleYPos = 25.0;
float space = 3.0;
float leftYPos = 0;
// locating Title label
float currentHeight = [self calculateHeightOfTextFromWidth:objectTBD.Title :titleFont :titleLabelWidth :UILineBreakModeWordWrap];
UILabel *cardTitle = [[UILabel alloc]initWithFrame:CGRectMake(leftGutter, titleYPos, titleLabelWidth, currentHeight)];
cardTitle.lineBreakMode = UILineBreakModeWordWrap;
cardTitle.numberOfLines = 0;
cardTitle.font = titleFont;
cardTitle.text = objectTBD.Title;
cardTitle.backgroundColor = [UIColor clearColor];
[imageView addSubview:cardTitle];
[cardTitle release];
leftYPos = titleYPos + currentHeight + space;
// locating brown line
UIView *brownLine = [[UIView alloc] initWithFrame:CGRectMake(5.0, leftYPos, 150.0, 2.0)];
brownLine.backgroundColor = [UIColor colorWithRed:0.647 green:0.341 blue:0.122 alpha:1.0];
[imageView addSubview:brownLine];
[brownLine release];
leftYPos = leftYPos + 2 + space + space + space;
// creating the imageView to place the image
UIImageView *processPhoto = [[UIImageView alloc] initWithFrame:CGRectMake(leftGutter, leftYPos, 150, 150)];
if((uniqueIndex == indx) && (uniqueImg.imageData != nil))
{
if([uniqueImg.rcpIden isEqualToString:objectTBD.iden])
{
objectTBD.imageData = [NSString stringWithFormat:#"%#", uniqueImg.imageData];
[recipesFound replaceObjectAtIndex:indx withObject:objectTBD];
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
[imageData release];
processPhoto.image = rcpImage;
[rcpImage release];
}
}
else if(objectTBD.imageData != nil)
{
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
processPhoto.image = rcpImage;
[rcpImage release];
[decodedBigImageDataPointers addObject:imageData];
}
else
{
UIImage * rcpImage = [UIImage imageNamed:#"default_recipe_img.png"];
processPhoto.image = rcpImage;
[rcpImage release];
}
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 1
[imageView addSubview:processPhoto];
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 2!!!!
//[processPhoto release]; // this line causes an error :(
// converting the UIImageView into a UIImage
UIGraphicsBeginImageContext(imageView.bounds.size);
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[objectTBD release];
for(UIView *eachSubview in imageView.subviews)
{
[eachSubview removeFromSuperview];
NSLog(#"each subview retainCount %i despues", [eachSubview retainCount]);
// here I find that the processPhoto view has a retain count of 2 (all other views have their retain count in 1)
}
return viewImage;
}
When I checked at the instruments object allocation I found that the "GeneralBlock-9216" growing up.
Breaking down the row I found that every time I call this code, one instance of:
2 0x5083800 00:18.534 ImageIO initImageJPEG
is being allocated. Checking the call stack, the following line is highlighted:
UIImage * objImage = [UIImage imageWithData:imageData];
Any help to find what the error is?
As TechZen said, the imageWithXXX: methods cache the image inside of them while you run the program (though you release the instances after using). I recommend initWithXXX: and release API sets instead of imageWithXXX:.
Well, if you embed several debug log on your source code, check how many times is it called, and check the retain count of the instances.
As far as I can explain, that is all.
I hope you will solve the problem.
Does anyone have an answer for this? It's tearing me apart trying to figure out why this image information keeps lingering. I've tried every solution.
The situation:
Images get downloaded and stored to the device, then loaded with imageWithContentsOfFile (or even initWithContentsOfFile, which doesn't help either). When the view goes away, the images don't, but they don't show up as leaks, they're just this initImageJPEG Malloc 9.00 KB that never goes away and keeps ramping up.
UPDATE: I believe I've figured this out: Check to make sure everything is actually getting dealloc'd when you're releasing whatever the parents (and/or grandparents) and etc of the images are. If the parents don't get deallocated, they never let go of their children images, and whatever data was in those images sticks around. So check retain counts of parent objects and make sure that everything's going away all the way up whenever you release the view at the top.
A good way to check for this is to put NSLogs into custom classes' dealloc methods. If they never show up, that object isn't going away, even though the reference to it might, and it (and whatever its subviews and properties are) will never ever disappear. In the case of images, this means a pretty sizable allocation every time that object is generated and never deallocated. It might not show up in leaks, especially if the parent of the topmost object you're thinking you're releasing but actually aren't persists and doesn't itself ever deallocate.
I hope this helps. It'll be useful to take some time to read through your code with a fine-toothed comb to make sure you're allocating and releasing things properly. (Search for "alloc]", start at the top of the file, and work your way down to make sure you're releasing and that the release isn't inside of some if() or something.)
Also, running "Build and Analyze" might lock up your machine for a bit, but its results can be really helpful.
Good luck!
I think you're seeing UIImage cacheing images. There used there used to be a method something like initWithData:cache that let you turn the cacheing off. Now I think the system always caches the images automatically behind the scenes even after you've deallocted the specific instances.
I don't think its an error on your part. I think it's the system keeping data around in the OpenGl subsystem. Unless it causes a major leak, I don't think it is a problem.