I'm trying to developed a really simple browser to be part of an iPad app. That browser will have tabs. All tabs are based on a .xib with an UIWebView filling almost all .xib frame.
I store all them inside an NSMutableArray, called tabsArray.
So, here's how I add a new tab to the array:
declaration:
.h
#property (nonatomic,strong) NSMutableArray *tabsArray;
#property (nonatomic,strong) UIDetailWebController *pagina;
.m
self.pagina = [[UIDetailWebController alloc] initWithNibName:#"UIDetailWebController" bundle:nil];
[tabsArray insertObject:pagina atIndex:currentViewTag+1];
how I display it on screen:
[self.view addSubview:[[tabsArray objectAtIndex:currentViewTag+1] view]];
And, finally, here's how I'm trying to "release" the UIWebView when user closes a tab (with a specific index):
[[[tabsArray objectAtIndex:index] view] removeFromSuperview];
[[tabsArray objectAtIndex:index] setWebView:nil];
[[tabsArray objectAtIndex:index] setView:nil];
[tabsArray removeObjectAtIndex:index];
My problem is: It appears that by doing this I simply don't release it. Memory consumption keeps the same, and If I'm playing an youtube video, the audio continues to play.
I'm kind of new on programming, and started iOS development by iOS 5, with ARC, so probably I'm letting slide some basic detail related to memory management.
SOLUTION:
Ok, I found out that what was retaining webview was the implementation of PullToRefreshView (https://github.com/chpwn/PullToRefreshView). When I set it's delegate to nil; everything just works!
The property pagina is is a strong property, so it retains the value that you assign to it in:
self.pagina = [[UIDetailWebController alloc] initWithNibName:#"UIDetailWebController" bundle:nil];
So you should set it to nil:
[tabsArray removeObjectAtIndex:index];
self.pagina = nil; // Add this line.
Finally, not that the following lines may not be needed:
[[tabsArray objectAtIndex:index] setWebView:nil];
[[tabsArray objectAtIndex:index] setView:nil];
And that the line:
[[[tabsArray objectAtIndex:index] view] removeFromSuperview];
may be simplified into:
[self.pagina removeFromSuperview];
I think the problem is that you store all your UIDetailWebControllers in an array. The array is keeping a strong pointer to your DetailWebController.
You should use:
[tabsArray removeObjectAtIndex:index];
And then set
self.pagina = nil;
After calling [self.pagina removeFromSuperView]; it should work.
Related
I have a little issue from removeFromSuperView as it's not at least working with iPad2 with iOS 5. I'm displaying a custom UIView alert before my table populates with data and once done, I'm removing it from the super view. with every other device it's working fine except in iPad2 (iOS5). Am I missing anything?
.h
#private
SaveUIDisplayViewController *wbsSummaryLoadView;
.m
// Displaying
wbsSummaryLoadView = [[SaveUIDisplayViewController alloc] initWithNibName:#"SaveUIDisplayViewController" bundle:nil];
[[wbsSummaryLoadView view] setFrame:[self view].bounds];
[wbsSummaryLoadView setupSavingViewWithTitle:NSLocalizedString(#"Loading...", #"")];
[[self view] addSubview:wbsSummaryLoadView.view];
// Removing
[wbsSummaryLoadView.view removeFromSuperview];
[wbsSummaryLoadView release];
wbsSummaryLoadView = nil;
Once you add a view you can release it right away. (a copy is creating by using addSubview)
Try:
[[self view] addSubview:wbsSummaryLoadView.view];
[wbsSummaryLoadView release];
and then you don't need to care, because if you release whole view you release that added view as well.
You are storing two different objects. When you create the SaveUIDisplayViewController, you are declaring a new instance in that method. When you are removing it, you are using (I guess) the member variable.
SaveUIDisplayViewController *wbsSummaryLoadView = [[SaveUIDisplayViewController alloc] initWithNibName:#"SaveUIDisplayViewController" bundle:nil];
This will hide any declaration of wbsSummaryLoadView that you have defined in the header of the object.
And when you come to remove it
[wbsSummaryLoadView.view removeFromSuperview];
Has to have wbsSummaryLoadView declared somewhere so (this is where I'm guessing as you haven't posted your .h file) if this is declared in the header, then it won't be the same as the one when you created it (in fact it will probably be nil at this point)
Happy November to all,
Well I tried Xcode Build and analyze on my project, and it showed some unusual leaks, which I couldn't quite accept with my knowledge of Objective C.
So I decided to put up a test project and ask here..
MemoryTestController.h
#interface MemoryTestController : UIViewController{
UIImageView *tstImageView;
}
#property(nonatomic,retain) UIImageView *tstImageView;
#end
MemoryTestController.m
#implementation MemoryTestController
#synthesize tstImageView;
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release];
}
-(void)dealloc{
[tstImageView release];
[super dealloc];
}
#end
When I try Build and analyze, clang static analyzer say
Potential leak of an object at line xx
And the culprit line is
self.tstImageView = [[UIImageView alloc]initWithFrame:<SomeFrame>];
I think I am releasing once for every time I am allocing/retaining. Am I missing something, or Static analyzer has some bugs?
EDIT : Is there any leak there?
Well I run the above project using Leak tool in instrument..It didn't show any leak even though I tried many times..Whom should I believe? Static analyzer or Leak instrument?
your problem is how you release it:
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release]; // << here
}
you should do it this way:
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
imageView.image = [UIImage imageNamed:#"SomeImage.png"];
self.tstImageView = imageView;
[imageView release];
[self.view addSubview:self.tstImageView];
}
The checker is correct because it cannot assume that the variable is identical to the one you set. Therefore, the form you use in the OP could introduce a reference count imbalance because the ivar's value may not be what you assigned to it by the time you message release upon the ivar.
These cases are not likely for a UIImageView, and quite unlikely in the context of your program, but these examples should give you an idea as to why the checker assumes that object->ivar associations shall not be trusted:
Between creation of the image view and the message to release it via the ivar, you have:
self.tstImageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
1) assignment of the image view via the setter
2) access of the image view via the getter
3) direct access of the ivar, when adding to self.view
the setter may have taken a copied or used a cached value. UIImageView is a bad example, but the checker does not know how types are generally passed around - even if it did, it would (at times) make unsafe assumptions.
the simplest example would be:
- (void)setName:(NSString *)inName {
NSString * prev = name;
if (inName == prev) return;
if (0 == [inName count]) name = #"";
else name = [inName copy];
[prev release];
}
the value held by the ivar could change in the meantime. not likely an issue in this case, but let's say that adding the image view as the subview could end up calling back and altering self in the process/effect of adding the subview, and replacing or removing the image view you passed. In that case, the variable view you passed would leak and the view it replaced it with would have a negative imbalance.
Neither of those are likely to happen in your example, but it does happen in real world programs, and the checker is correctly evaluating based on locality, not property (the checker can't assume much of what happens inside a method call). It also encourages one good idiomatic style in this case.
EDIT : Is there any leak there?
Well I run the above project using
Leak tool in instrument..It didn't shown any leak even though I tried
it many times..Whom should I believe? Static analyzer or Leak
instrument?
The static analyzer says there is a potential leak because it is unable to guarantee the reference/allocation it follows is correctly retained/released. You can guarantee that reference counting is correct and please the static analyzer by changing you program to look like I wrote it in my example.
The way you have written it has made it impossible for the analyzer to follow the reference.
If you have no leaks and no zombies, then there is not a leak. But the solution is easy to fix - and programs have a way of changing during development. It's much easier to use the form I posted so it is easier for the toolset and for you to verify the program is correct. The static analyzer is not always correct, but you should adjust your programs to please it because static analysis is very useful. The program I posted is also easier for a human to understand and confirm that it is correct.
when you declare a property with retain like this
#property(nonatomic,retain) UIImageView *tstImageView;
a setter is added that will incr the retainCount when you assign to the property. When you do as below the object you created has already a retainCount == 1
self.tstImageView = [[UIImageView alloc]
initWithFrame:<SomeFrame>];
so the tstImageView object has 2 in retainCount.
do instead
UIImageView* view = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView = view;
[view release];
then, although unrelated to your leak when you release it write like this instead
self.tstImageView = nil;
since the setter will then will properly set the retainCount
I have a problem with my app where the code for which is far too long to go into, but suffice to say when i'm removing a UIView and replacing it with a new one like so:
NSLog(#" .. %#", (Icon *)[self viewWithTag:index]);
Icon *icon = (Icon *)[self viewWithTag:index];
CGRect frame = icon.frame;
int tag = icon.tag;
[icon removeFromSuperview];
[icon release];
Icon *icon2 = [[Icon alloc] init];
icon2.frame = frame;
[icon2 makeIconStandardWithTag:(int)tag];
[self addSubview:icon2];
It does some weird thing where that NSLog the first time (because the view is already there) shows that the object is an icon, but the second time after running this code shows that it's a UIImageView for some reason now, and it displays what i presume to be the original icon at some odd position on the screen. It's very erratic behaviour. But what i do know is this:
Removing the [icon removeFromSuperview]; line, although keeping the object there, stops this behaviour and causes the NSLog to return an Icon, as it should.
So my guess is that it's not removing icon correctly. Is there a way to completely remove icon, or is removeFromSuperview as far as i can go. What i could do is just have it set to alpha = 0 but this is more of a patch-over solution and not how i want to solve it.
"Is there a way to completely remove
icon, or is removeFromSuperview as far
as i can go"
You can set the object to nil:
icon = nil;
Can you verify what "self" is in this line of code:
It might not be what you think.
[self addSubview:icon2];
NSLog(#" Self is %#", self);
This is a guess, but try setting self.tag to -1 or some other value that doesn't collide with the tags you're setting on your Icon objects. The viewWithTag: method searches the current view and its subviews for a match, so if self.tag == 0 and you call [self viewWithTag:0], you'll get self.
Did you retain icon somewhere prior to this? If not, no need to release it after the call to removeFromSuperview. Similarly, unless you need the reference to icon2 elsewhere, you can release that after calling addSubview.
Views retain views added via addSubview, and they release views removed via removeFromSuperview.
I'm using TTLauncherView as a sort of home screen for my app and I only have one page's worth of icons. How can I make it so the TTLauncherView won't let you drag icons to "the next page"? I want to set a maximum number of pages (in this case one.)
(EDIT: long story short, I subclassed beginEditing, see the answer below.)
I see where why it adds an extra page when beginEditing is called, but I don't want to edit the framework code. (That makes it hard to update to newer versions.) I'd also prefer not to subclass and override that one method, if I have to rely on how it's implemented. (I'm not against subclassing or adding a category if it's clean.)
I tried setting scrollView.scrollEnabled to NO in the callback method launcherViewDidBeginEditing in my TTLauncherViewDelegate, but that doesn't work while it's in editing mode and I don't know why.
I tried adding a blocker UIView to the scrollview to intercept the touch events by setting userInteractionEnabled=NO, which works OK. I still have to disable the dragging of TTLauncherItems to the next page somehow.
I also tried setting the contentSize of the scrollview to it's bounds in launcherViewDidBeginEditing, but that didn't seem to work either.
Is there a better way?
Tried to block gestures:
- (void)setLauncherViewScrollEnabled:(BOOL)scrollEnabled {
if (scrollEnabled) {
[self.scrollViewTouchInterceptor removeFromSuperview];
self.scrollViewTouchInterceptor = nil;
} else {
// iter through the kids to get the scrollview, put a gesturerecognizer view in front of it
UIScrollView *scrollView = [launcherView scrollViewSubview];
self.scrollViewTouchInterceptor = [UIView viewWithFrame:scrollView.bounds]; // property retains it
UIView *blocker = self.scrollViewTouchInterceptor;
[scrollView addSubview:scrollViewTouchInterceptor];
[scrollView sendSubviewToBack:scrollViewTouchInterceptor];
scrollViewTouchInterceptor.userInteractionEnabled = NO;
}
}
For reference: TTLauncherView.m:
- (void)beginEditing {
_editing = YES;
_scrollView.delaysContentTouches = YES;
UIView* prompt = [self viewWithTag:kPromptTag];
[prompt removeFromSuperview];
for (NSArray* buttonPage in _buttons) {
for (TTLauncherButton* button in buttonPage) {
button.editing = YES;
[button.closeButton addTarget:self action:#selector(closeButtonTouchedUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
}
// Add a page at the end
[_pages addObject:[NSMutableArray array]];
[_buttons addObject:[NSMutableArray array]];
[self updateContentSize:_pages.count];
[self wobble];
if ([_delegate respondsToSelector:#selector(launcherViewDidBeginEditing:)]) {
[_delegate launcherViewDidBeginEditing:self];
}
}
I think overriding beginEditing in TTLauncherView is your best bet. Since you'd only really be touching one method (and only a few lines in that method), upgrading it when the time comes shouldn't be too bad. Since that method explicitly adds the extra page, I'm not sure how you'd get around it w/o editing that specific piece of code
As Andrew Flynn suggested in his answer, I was able to make it work by subclassing and overriding the beginEditing method to remove the extra page TTLauncherView adds when it goes into editing mode.
One problem I'm having is I can't figure out how to remove the warning I get for calling the (private) method updateContentSize on my subclass. I tried casting it to id, but that didn't remove the warning. Is it possible?
edit: I was able to remove the warning by using performSelector to send the message to the private class. (I had previously create a category method performSelector:withInt that wraps NSInvocation so that I can pass primitives via performSelector methods, which makes this very convenient.)
// MyLauncherView.h
#interface MyLauncherView : TTLauncherView {
NSInteger _maxPages;
}
#property (nonatomic) NSInteger maxPages;
#end
// MyLauncherView.m
#implementation MyLauncherView
#synthesize maxPages = _maxPages;
- (void)beginEditing {
[super beginEditing];
// ignore unset or negative number of pages
if (self.maxPages <= 0) {
return;
}
// if we've got the max number of pages, remove the extra "new page" that is added in [super beginEditing]
if ([_pages count] >= self.maxPages ) {
[_pages removeLastObject];
[self updateContentSize:_pages.count]; // this has a compiler warning
// I added this method to NSObject via a category so I can pass primitives with performSelector
// [self performSelector:#selector(updateContentSize:) withInt:_pages.count waitUntilDone:NO];
}
}
I am working on an iPhone app which is related to stock market.
There is a requirement to create a horizontal scroller which is similar to a stock ticker.
What should I do to achieve that?
This sort of thing is a damn nuisance and (as far as I know) there is no easy existing way.
First you must simplify K.I.S.S. by making each block the same length. Here's how to do it..
(1) make a standard width (say, 100 px) for each "block".
The blocks would simply be UIViews (or perhaps just text labels). Are you comfortable with creating UIViews on the fly? If not we will tell you how to do it!
(2) pick a start point for the journey which is just to the right completely offscreen
(3) pick an end point for the journey which is just to the left completely offscreen
(3b) choose the exact time (say "4.71937s") you want for the travel time from point1 to point2
(4) figure out precisely (as in exactly precisely!) the seconds it takes for the block to travel it's own length from right to left. let's say 0.91763 seconds
You have to use NSTimers to do this. Are you familiar with them?
(5) set up a recurring 0.91763s timer which: makes a new block and
(5b) sucks off the next, if any, piece of text information (say "AAPL 408.50") and
(5c) places it at the start point2 and
(5d) simply using core animation begins it animating towards the end point3 and
(5e) launch an individual one shot timer for this block, which will destroy this block after the overall time mentioned in 3b
That's it.
You will have to set up a simple FIFO stack for the text items (whatever you you choose), as you get them from whatever source, shove them in to it.
Are you comfortable setting up some sort of array to use as a stack of info? Again if not we can help! :)
Note that in (5b) you would likely shove the one you just used back on the other end of the stack so they all keep going forever. It's quite likely you would have to be able to touch individual items to delete them or, eg, modify the price or whatever as new info comes in.
Once you get this working ("standard block length method"). You may prefer to somehow work out the exact length of each text block (AAPL 400.50 is longer than AAPL 30)...to do so...
Calculate on the fly a new own-length time value (as in point 4) for each block just for that block. ie, do that at point 5.b.2. instead of using a recurring timer (in point 5), fire off a new timer (at 5f) to launch the next block. (To be clear, points 3b and 5e are unchanged.)
Hope it helps!
If I understand the question aright, then one simple solution is just to update a UILabel using an NSTimer (see quickly knocked up code below). For some applications this might be enough, but if you need to keep updating the string with fresh data then you'll need to do some more work, e.g. so the new string is appending 'off screen' as it were.
This simple solution does not give you particularly smooth scrolling. Rather it jumps one character width at a time and, with the default system font, not every character is as wider as every other.
To do smoother scrolling, you could render the string into a graphics context (use drawInRect:withFont: on an NSString), make a UIImage, and then nudge that along by n pixels at a time. When you get near to the right margin of the image, you'll need to render the image a second time to the right of the end of the first copy.
Here's the simple code (with .h and .m files combined) that demonstrates the crude approach:
// stock ticker example code
//
// Created by Matthew Elton on 27/12/2010.
// http://www.obliquely.org.uk/blog/app All rights reserved.
//
#define TICKER_WIDTH 250.0
#define TICKER_FONT_SIZE 18.0
#define TICKER_RATE 0.2 // nudge along five times a second
#import <UIKit/UIKit.h>
#interface tickerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
NSString* tickerString;
UILabel *tickerLabel;
}
#property (nonatomic, retain) NSString* tickerString;
#property (nonatomic, retain) UILabel* tickerLabel;
#property (nonatomic, retain) IBOutlet UIWindow *window;
- (void) nudgeTicker: theTimer;
#end
#implementation tickerAppDelegate
#synthesize window, tickerString, tickerLabel;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setTickerString: #"This is the sample string to use on the ticker. It is quite long. So I need to fill it out with random content. "];
CGSize labelContentSize = [[self tickerString] sizeWithFont: [UIFont systemFontOfSize:TICKER_FONT_SIZE] forWidth: 2000.0 lineBreakMode: UILineBreakModeClip];
[self setTickerLabel: [ [ [UILabel alloc] initWithFrame:CGRectMake(20,40,TICKER_WIDTH, labelContentSize.height)] autorelease]];
[[self tickerLabel] setFont: [UIFont systemFontOfSize:TICKER_FONT_SIZE]];
[[self tickerLabel] setText: [self tickerString]];
[[self tickerLabel] setBackgroundColor:[UIColor lightGrayColor]];
[[self tickerLabel] setLineBreakMode:UILineBreakModeClip];
[NSTimer scheduledTimerWithTimeInterval:TICKER_RATE target:self selector: #selector(nudgeTicker:) userInfo:nil repeats:YES];
[window addSubview:[self tickerLabel]];
[self.window makeKeyAndVisible];
return YES;
}
- (void) nudgeTicker: theTimer;
{
NSString* firstLetter = [[self tickerString] substringWithRange: NSMakeRange(0,1)];
NSString* remainder = [[self tickerString] substringWithRange:NSMakeRange(1,[[self tickerString] length]-1)];
[self setTickerString: [remainder stringByAppendingString: firstLetter]];
[[self tickerLabel] setText:[self tickerString]];
}
- (void)dealloc {
[window release];
[tickerString release];
[tickerLabel release];
[super dealloc];
}
#end