upon dismissing my VC, I noticed I am not releasing everything from memory. I'm very lost as to how I would go about finding my retain cycle. I am using an NSTimer and NSNotificationCenter, but I make sure to invalidate and removeObservers before exiting, and I made sure of using a weak delegate.
Where else could my retain cycles be occurring? In animation blocks? Like this?
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.setListTableViewVertConst.constant = 0;
self.setListTableViewHeightConst.constant = 264;
} completion:^(BOOL finished) {
}];
When using GCD I make sure to use weakSelf.
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.remotePasswordTextField resignFirstResponder];
});
Thanks for any help.
EDIT:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//Send the room code to be displayed on the respective view controllers.
if ([segue.identifier isEqualToString:#"toSetListRoomVC"]) {
SetListRoomViewController *setListVC = segue.destinationViewController;
setListVC.roomCode = self.roomCodeTextField.text;
}
}
viewWIllApear
[super viewWillAppear:YES];
self.socket = [[SocketKeeperSingleton sharedInstance]socket];
self.socketID = [[SocketKeeperSingleton sharedInstance]socketID];
NSString *roomCodeAsHost = [[SocketKeeperSingleton sharedInstance]hostRoomCode];
/////////HOST/////////
if ([[SocketKeeperSingleton sharedInstance]isHost]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveHostSongAddedNotification:)
name:kQueueAdd
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveUserJoinedNotification:)
name:kUserJoined
object:nil];
NSLog(#"User is the host of this room");
self.isHost = YES;
[self viewForNoCurrentArtistAsHost];
self.roomCodeLabel.text = roomCodeAsHost;
if (!self.hostQueue) {
self.hostQueue = [[NSMutableArray alloc]init];
}
if (!self.hostCurrentArtist) {
self.hostCurrentArtist = [[NSMutableDictionary alloc]init];
}
if (!self.player) {
self.player = [[AVPlayer alloc]init];
}
if (!self.timer) {
self.timer = [[NSTimer alloc]init];
}
}
///////NOT HOST///////
else {
// Add a notifcation observer and postNotification name for updating the tracks.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveQueueUpdatedNotification:)
name:kQueueUpdated
object:nil];
//Add a notifcation observer and postNotification name for updating current artist.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveCurrentArtistUpdateNotification:)
name:kCurrentArtistUpdate
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveOnDisconnectNotification:)
name:kOnDisconnect
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveHostDisconnectNotification:)
name:kHostDisconnect
object:nil];
//Add some animations upon load up. Purple glow and tableview animation.
double delay = .4;
[self purpleGlowAnimationFromBottomWithDelay:&delay];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationBottom];
//Set Current artist, if there is one.
NSDictionary *currentArtist = [[SocketKeeperSingleton sharedInstance]currentArtist];
[self setCurrentArtistFromCurrentArtist:currentArtist];
//Set the current tracks, if there is one.
NSArray *setListTracks = [[SocketKeeperSingleton sharedInstance]setListTracks];
if (setListTracks) {
self.tracks = setListTracks;
}
}
}
tableVIEWs
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView.tag == 1) {
if (self.isHost) {
return [self.hostQueue count];
}
else return [self.tracks count];
}
else return [self.searchTracks count];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
I noticed you are using the word "popOut" in some comments, so I'm going to assume that you are using a navigation controller.
If that is the case, your view controller is retained by the nav controller it's been embedded in and is not released. The nav controller needs to hold a reference to your VC (see UINavigationController.viewControllers)so that it can get back to it when you pop out the top/next VC in the hierarchy.
This is expected behavior.
In this code sample you are not using __weak for self
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.setListTableViewVertConst.constant = 0;
self.setListTableViewHeightConst.constant = 264;
} completion:^(BOOL finished) {
}];
However, at a glance this does not seem to be creating a retain cycle. The code you posted doesn't seem to have any, so it's probably somewhere else. Are you sure you have retain cycles?
Anyway, try instrumenting you app. It might help.
Related
I've been struggling with this now for a long time. Where is my mistake? At the moment the hide and show seems to work, but everytime I come "back" to my viewcontroller I see that my view is shifted up, but there is no ad in there. But the first time I see the view controller, there is an ad. What am I doing wrong?
I just want to show the same ad across view controllers and this is like the parent UIViewController class, a lot of other view controllers inherit from:
#pragma mark View lifecycle
-(void)viewDidLoad
{
[super viewDidLoad];
if(![[NSUserDefaults standardUserDefaults] objectForKey:kInAppPurchaseNoAds]){
self.bannerContainer = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).bannerView;
self.bannerContainer.frame = CGRectOffset(self.bannerContainer.frame, 0, self.view.frame.size.height);
[self.view addSubview:self.bannerContainer];
}
}
//Handle the in app purchases
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//iAd
if(![[NSUserDefaults standardUserDefaults] objectForKey:kInAppPurchaseNoAds] && !self.bannerContainer){
self.bannerContainer.delegate = self;
}
if(self.bannerContainer.bannerLoaded){
[self showBanner];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkIAdPurchase) name:IAPHelperProductPurchasedNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self hideBanner];
self.bannerContainer.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:IAPHelperProductPurchasedNotification object:nil];
}
#pragma mark Check iAd purchase
-(void)checkIAdPurchase
{
if([[NSUserDefaults standardUserDefaults] objectForKey:kInAppPurchaseNoAds] && self.bannerContainer){
[self hideBanner];
[self.bannerContainer removeFromSuperview];
self.bannerContainer.delegate = nil;
self.bannerContainer = nil;
}
}
#pragma mark IAd delegate
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
[self showBanner];
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
[self hideBanner];
}
#pragma mark Show and hide the banner
- (void)showBanner
{
if(!self.isBannerVisible){
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.5
animations:^{
//Restore the constraint
self.mainViewBottomConstraint.constant = 50;
//Move the banner on
self.bannerContainer.frame = CGRectOffset(self.bannerContainer.frame, 0, -50);
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.isBannerVisible = YES;
}];
}
}
- (void)hideBanner
{
if(self.isBannerVisible){
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.5
animations:^{
//Restore the constraint
self.mainViewBottomConstraint.constant = 0;
//Move the banner off
self.bannerContainer.frame = CGRectOffset(self.bannerContainer.frame, 0, self.bannerContainer.frame.size.height);
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.isBannerVisible = NO;
}];
}
}
Actually you are creating instance of iAd in AppDelegate and that's only one in the whole app.Now when you first time come to Vc then due to code in viewdidload it add to your Vc..
Now when you move to other view controller that viewcontroller also add iAd at viewdidload..You have only one object of iAd so it will add to at a time in One VC..So when you move to other Vc iAd will remove from that Vc and add to new VC..
Solution : You should call iAD subview code in ViewWillAppear...
I have a message view in which user can see conversation and type & send message. Menu (kind of context menu) is displayed when user long taps on message. I have used TableView and Button for chat history and send message button respectively.
My problem is canPerformAction:withSender method is hit when I tap on Button or TableView. And moreover this code was working fine till iOS 9.0.
#interface MessageDetailViewController ()
- (void)updateDetailView;
- (void)setMessageBar;
- (void)setCallButton;
#end
#implementation MessageDetailViewController
{
}
#synthesize messageField;
#synthesize messageList;
#synthesize messageBarView;
#synthesize navigationBarView;
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"MessageDetailView: viewDidLoad method called.");
// Observe keyboard hide and show notifications to resize the text view appropriately.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didHideEditMenu:) name:UIMenuControllerDidHideMenuNotification object:nil];
[messageField becomeFirstResponder];
messageArray = [[NSMutableArray alloc] init];
typingImage = [[UIImageView alloc] init];
presenceImage = [[UIImageView alloc] init];
navigationBarView = [[UIView alloc] init];
viewHeight = self.view.frame.size.height;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[messageField setDelegate:self];
messageList.delegate = self;
messageList.dataSource = self;
[self updateDetailView];
[messageList reloadData];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
/**
* This method is called to send instance method to server.
*/
- (void)onSendMessageButtonPressed:(id)sender
{
if([messageField.text length] == 0)
{
return;
}
messageField.text = [messageField.text substringToIndex:160];
[[DatabaseManager getDatabaseManager] addMessageInDatabase:messageField.text];
[messageField setText:#""];
[self updateDetailView];
[messageList reloadData];
}
-(void)handleLongPress : (UILongPressGestureRecognizer *)gesture
{
TableViewCell *cell = nil;
UIMenuController *menu = [UIMenuController sharedMenuController];
if(gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint p = [gesture locationInView:self.messageList];
NSIndexPath *ip = [self.messageList indexPathForRowAtPoint:p];
rowPosition = ip.row;
NSLog(#"row = %d", ip.row);
if(ip)
{
cell = (TableViewCell *)[messageList cellForRowAtIndexPath:ip];
[self becomeFirstResponder];
[menu setMenuItems:nil];
UIMenuItem *copyMenuItem = [[[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"COPY_MESSAGE", #"") action:#selector(copyMessage:)] autorelease];
UIMenuItem *deleteMenuItem = [[[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"DELETE_MESSAGE", #"") action:#selector(deleteMessage:)] autorelease];
if(cell.imFailIndication.image != nil)
{
UIMenuItem *resendMenuItem = [[[UIMenuItem alloc] initWithTitle:NSLocalizedString(#"RESEND_MESSAGE", #"") action:#selector(resendMessage:)] autorelease];
[menu setMenuItems:[NSArray arrayWithObjects:copyMenuItem, deleteMenuItem, resendMenuItem, nil]];
}
else
{
[menu setMenuItems:[NSArray arrayWithObjects:copyMenuItem, deleteMenuItem, nil]];
}
[menu setTargetRect:cell.frame inView:cell.superview];
[menu setMenuVisible:YES animated:YES];
return;
}
}
}
- (void)copyMessage:(id)sender
{
MessageDetailInfo *detailMessageInfo = [messageArray objectAtIndex:rowPosition];
[UIPasteboard generalPasteboard].string = detailMessageInfo.message;
}
- (void)deleteMessage:(id)sender
{
messageDeleteActionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(#"PROMPT_MESSAGE_DELETE_CONFIRMATION", #"") delegate:self cancelButtonTitle:NSLocalizedString(#"CANCEL_BTN_TITLE", #"") destructiveButtonTitle:NSLocalizedString(#"DELETE_BTN_TITLE", #"") otherButtonTitles:nil, nil];
[messageDeleteActionSheet showFromTabBar:self.tabBarController.tabBar];
}
/*
The view implements this method to conditionally enable or disable commands of the editing menu.
The canPerformAction:withSender method is declared by UIResponder.
*/
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
UIMenuController *menu = (UIMenuController *)sender;
switch ([[menu menuItems] count])
{
case 0:
//This case is to display paste option for Message text field.
if(action == #selector(paste:))
{
return YES;
}
break;
case 2:
if(action == #selector(copyMessage:) || action == #selector(deleteMessage:))
{
return YES;
}
break;
case 3:
if(action == #selector(copyMessage:) || action == #selector(deleteMessage:) || action == #selector(resendMessage:))
{
return YES;
}
break;
default:
break;
}
return NO;
}
- (void)keyboardWillShow:(NSNotification *)notification
{
/*
Reduce the size of the text view so that it's not obscured by the keyboard.
Animate the resize so that it's in sync with the appearance of the keyboard.
*/
NSDictionary *userInfo = [notification userInfo];
// Get the origin of the keyboard when it's displayed.
NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
// Get the top of the keyboard as the y coordinate of its origin in self's view's coordinate system. The bottom of the text view's frame should align with the top of the keyboard's final position.
CGRect keyboardRect = [aValue CGRectValue];
// Get the duration of the animation.
NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration;
[animationDurationValue getValue:&animationDuration];
// Animate the resize of the text view's frame in sync with the keyboard's appearance.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:animationDuration];
/* change position of messageBarView based on visibility of keypadview. */
CGRect messageBarFrame = messageBarView.frame;
messageBarFrame.origin.y = viewHeight - keyboardRect.size.height - messageBarView.frame.size.height;
messageBarView.frame = messageBarFrame;
if(messageBarFrame.origin.y == (viewHeight - keyboardRect.size.height - messageBarView.frame.size.height))
{
[messageField becomeFirstResponder];
}
/* change height of messageList based on visibility of keypadView. */
CGRect messageListFrame = messageList.frame;
messageListFrame.size.height = messageBarView.frame.origin.y;
messageList.frame = messageListFrame;
[UIView commitAnimations];
/* scroll to table view at last message if messages are available. */
if ([messageList numberOfRowsInSection:0])
{
[messageList scrollToRowAtIndexPath:[NSIndexPath indexPathForRow ([messageList numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
- (void)keyboardWillHide:(NSNotification *)notification
{
NSDictionary* userInfo = [notification userInfo];
/*
Restore the size of the text view (fill self's view).
Animate the resize so that it's in sync with the disappearance of the keyboard.
*/
NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration;
[animationDurationValue getValue:&animationDuration];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:animationDuration];
/* change position of messageBarView based on visibility of keypadview. */
CGRect newFrame = messageBarView.frame;
newFrame.origin.y = self.view.frame.size.height - messageBarView.frame.size.height;
messageBarView.frame = newFrame;
/* Don't update tableview if datasource has been set nil. */
if(messageList.dataSource != nil)
{
/* change height of messageList based on visibility of keypadView. */
CGRect messageListFrame = messageList.frame;
messageListFrame.size.height = messageBarView.frame.origin.y;
messageList.frame = messageListFrame;
}
[UIView commitAnimations];
}
- (void)didHideEditMenu:(NSNotification *)notification
{
[[UIMenuController sharedMenuController] setMenuItems:nil];
}
- (void)didReceiveMemoryWarning
{
/* Releases the view if it doesn't have a superview. */
[super didReceiveMemoryWarning];
/* Release any cached data, images, etc that aren't in use. */
}
- (void)viewDidUnload
{
[super viewDidUnload];
/* Release any retained subviews of the main view.
e.g. self.myOutlet = nil; */
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerDidHideMenuNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
messageList.delegate = nil;
messageList.dataSource = nil;
}
-(BOOL)navigationShouldPopOnBackButton
{
NSLog(#"navigationShouldPopOnBackButton appearedFromComposeView = %d", [[NSNumber numberWithBool:appearedFromComposeView] intValue]);
if(appearedFromComposeView)
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
return YES; // Process 'Back' button click and Pop view controler
}
- (void)dealloc
{
[messageField release];
[messageList release];
[messageBarView release]
[messageArray release];
[super dealloc];
}
#end
I've been developing an iPhone / iPad game using Sprite Kit and in between each round I load an interstitial advert.
The interstitial is loaded on the main GameViewController and sits on top of the skview. I use a series of observers to trigger and cancel adverts and this all seems to work fine.
However, I've noticed some serious memory issues and after 4 or 5 rounds the app will crash. It appears to be directly related to the iAd interstitial. I've attached my code and you can see that I'm deallocating the objects, but the memory foot print does not seem to drop. I am using ARC too.
Does anyone know what could be causing this issue? I did read here: iAd & AdMob Heavy on Memory that the webkit view seems to hold on to its contents. I need to find a way to fix this, my code for my GameViewController is as follows:
#pragma mark - GAME LOAD
-(void)loadStartScreen{
_theView = (SKView *) self.view;
_theView.showsFPS = YES;
_theView.showsNodeCount = YES;
//Sprite Kit applies additional optimizations to improve rendering performance
_theView.ignoresSiblingOrder = YES;
// Create and configure the scene.
_theScene = [MainMenuScene sceneWithSize:_theView.bounds.size];
_theScene.scaleMode = SKSceneScaleModeAspectFill;
_theScene.backgroundColor = [UIColor grayColor];
// Present the scene
[_theView presentScene:_theScene];
// setup observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(requestFullScreenAd) name:#"requestAdvert" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showFullScreenAd) name:#"showAdvert" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cancelAdverts) name:#"cancelAdvert" object:nil];
}
#pragma mark - ADVERT CREATION AND SUPPORT
-(void)requestFullScreenAd {
// run the process on the main thread in a background queue
dispatch_async(bGQueue, ^{
if (_requestingAd == NO) {
_interstitial = [[ADInterstitialAd alloc]init];
_interstitial.delegate = self;
self.interstitialPresentationPolicy = ADInterstitialPresentationPolicyManual;
NSLog(#"Ad Request");
_requestingAd = YES;
}
});
}
-(void)showFullScreenAd{
if (_adLoaded) {
CGRect interstitialFrame = self.view.bounds;
interstitialFrame.origin = CGPointMake(0, 0);
_adView = [[UIView alloc] initWithFrame:interstitialFrame];
[self.view addSubview:_adView];
[_interstitial presentInView:_adView];
_button = [UIButton buttonWithType:UIButtonTypeCustom];
[_button addTarget:self action:#selector(closeAd:) forControlEvents:UIControlEventTouchDown];
_button.backgroundColor = [UIColor clearColor];
[_button setBackgroundImage:[UIImage imageNamed:kCloseAd] forState:UIControlStateNormal];
_button.frame = CGRectMake(10, 10, 40, 40);
_button.alpha = 0.75;
[_adView insertSubview:_button aboveSubview:_adView];
[UIView beginAnimations:#"animateAdBannerOn" context:nil];
[UIView setAnimationDuration:1];
[_adView setAlpha:1];
[UIView commitAnimations];
}
}
-(void)closeAd:(id)sender {
[UIView beginAnimations:#"animateAdBannerOff" context:nil];
[UIView setAnimationDuration:1];
[_adView setAlpha:0];
[UIView commitAnimations];
_adView=nil;
_requestingAd = NO;
_button = nil;
_interstitial.delegate = nil;
_interstitial = nil;
// notification for ad complete
[[NSNotificationCenter defaultCenter] postNotificationName:#"adClosed" object:nil];
}
-(void)cancelAdverts{
[_interstitial cancelAction];
_adView=nil;
_requestingAd = NO;
_button = nil;
_interstitial.delegate = nil;
_interstitial = nil;
}
#pragma mark - IAD DELEGATE
-(void)interstitialAd:(ADInterstitialAd *)interstitialAd didFailWithError:(NSError *)error {
[_interstitial cancelAction];
_adView=nil;
_requestingAd = NO;
_button = nil;
_interstitial.delegate = nil;
_interstitial = nil;
NSLog(#"Ad didFailWithERROR");
NSLog(#"%#", error);
// request another advert if it failed
//[self requestFullScreenAd];
}
-(void)interstitialAdDidLoad:(ADInterstitialAd *)interstitialAd {
if (interstitialAd.loaded) {
_adLoaded = YES;
[[NSNotificationCenter defaultCenter]postNotificationName:#"adLoaded" object:nil];
}
NSLog(#"Ad DidLOAD");
}
-(void)interstitialAdDidUnload:(ADInterstitialAd *)interstitialAd {
[self closeAd:nil];
NSLog(#"Ad DidUNLOAD");
}
-(void)interstitialAdActionDidFinish:(ADInterstitialAd *)interstitialAd {
[self closeAd:nil];
NSLog(#"Ad DidFINISH");
}
Then in my level complete SKScene:
#pragma mark - SCENE APPEARS
-(void)didMoveToView:(SKView *)view {
// request an advert if advert removal is not purchased
if (![[[UserDetails sharedManager]iapAdsRemoved]boolValue]) {
// send request ad notification
[[NSNotificationCenter defaultCenter]postNotificationName:#"requestAdvert" object:nil];
// look for add loaded notification
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(adLoaded) name:#"adLoaded" object:nil];
// look for add completed
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(adShowCompleted) name:#"adClosed" object:nil];
}
// setup UI
[self createUI];
if (![[UnlockController sharedManager]allunlocksOpen]) {
// check all unlocks
[[UnlockController sharedManager]checkUnlocks:[[UserDetails sharedManager]userTotalScore]+[[UserDetails sharedManager]userLevelScore]];
// get the next unlock
[self getNextUnlockScore];
// set bar with correct increment
[unlockBar setBarValues:[[UserDetails sharedManager]userTotalScore]+[[UserDetails sharedManager]userLevelScore] increment:[[UserDetails sharedManager]userTotalScore] nextObject:nextScore];
}
else{
[self allUnlocksOpen];
}
// pre add button
preAdButtonPress = 3;
// variables
startCount = 0;
unlockItemsCount = 0;
allUnlocks = [[UnlockController sharedManager]finalUnlockOpen];
// start unlocks sequence
[self performSelector:#selector(runRewards) withObject:nil afterDelay:1.0];
}
-(void)willMoveFromView:(SKView *)view{
// cancel any adverts
[[NSNotificationCenter defaultCenter]postNotificationName:#"cancelAdvert" object:nil];
// remove observers
[[NSNotificationCenter defaultCenter]removeObserver:#"adClosed"];
[[NSNotificationCenter defaultCenter]removeObserver:#"adLoaded"];
}
Was a memory issue with some core code rather than the iad causing memory leaks.
My uitableview calls reloaddata when the orientation of the device changes (makes sense, since the number of cells displayed changes, is called in layout subviews as far as I could understand from the documentation), however this is problematic for me, because I am performing a download in the background and I don't want some of the files to suddenly appear. Is there a way to stop the default behaviour and let me reload manually when I want to?
EDIT:
I will try to explain better. on the top of my tableview, I have a button called "sync" which starts a syncing request from the server, this sync request first get a JSON object, which holds the information I would like to display in the tableview, but each of the uitableview items represents a file I'm downloading from the internet.
While the files are downloading I have an activity indicator on screen, only when the files finish downloading I want to reload the table. The problem is, the UITableview automatically calls reloaddata when the user changes orientation, so the cells fill with the information from the json before the downloaded files finished downloading.
code:
#implementation BIDManageFilesViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(newDataFinishedDownloading) name:kBIDContentManagerContentDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(newDataStartedDownloading:) name:kBIDContentManagerStartedDownloadingContent object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contentAlreadyUpToDate) name:kBIDContentUpToDate object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contentStartingSync) name:kBIDContentStartingSync object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contentEndingSync) name:kBIDContentEndingSync object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(singleDownloadFinished) name:kBIDFinishedDownloadingSingleContent object:nil];
self.navigationController.navigationBarHidden = NO;
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:#"Sync"
style:UIBarButtonItemStyleDone target:self action:#selector(Sync)];
self.navigationItem.leftBarButtonItem = leftButton;
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Display Mode"
style:UIBarButtonItemStyleDone target:self action:#selector(dismissSelf)];
self.navigationItem.rightBarButtonItem = rightButton;
self.navigationItem.title = #"Content Manager";
self.navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectZero];
[self.view addSubview:_navigationBar];
[self.navigationBar pushNavigationItem:self.navigationItem animated:NO];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
-(void)layoutNavigationBar{
if([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown)
{
self.navigationBar.frame = CGRectMake(0, self.tableView.contentOffset.y, self.view.frame.size.width, self.topLayoutGuide.length + 44);
}
else
{
self.navigationBar.frame = CGRectMake(0, self.tableView.contentOffset.y, self.view.frame.size.height, self.topLayoutGuide.length + 44);
}
NSLog(#"width: %f", self.view.frame.size.width);
NSLog(#"height: %f", self.view.frame.size.height);
self.tableView.contentInset = UIEdgeInsetsMake(self.navigationBar.frame.size.height, 0, 0, 0);
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//no need to call super
[self layoutNavigationBar];
}
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self layoutNavigationBar];
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self layoutNavigationBar];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a story board-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.navigationController.navigationBarHidden = YES;
}
-(void)newDataStartedDownloading: (NSNotification *)notif
{
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
_hud.labelText = #"Downloading...";
_hud.detailsLabelText = [NSString stringWithFormat:#"1/%#",[notif.userInfo objectForKey:#"downloadFileNumber"]];
}
-(void)singleDownloadFinished
{
NSString *currentText = _hud.detailsLabelText;
NSArray *subStrings = [currentText componentsSeparatedByString:#"/"];
NSInteger downloadsPlusOne = [[subStrings objectAtIndex:0] integerValue]+1;
NSString *newTextForLabel = [NSString stringWithFormat:#"%d/%#", downloadsPlusOne, [subStrings objectAtIndex:1]];
_hud.detailsLabelText = newTextForLabel;
}
-(void)newDataFinishedDownloading
{
_thereIsNewInfo = TRUE;
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self.tableView reloadData];
[[NSNotificationCenter defaultCenter] postNotificationName:kBIDnewDownloadedContentReadyToBeDispayedNotification object:nil userInfo:nil];
}
-(void)contentAlreadyUpToDate
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Sync Alert"
message:#"Files Are Already Up To Date"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
}
-(void)contentStartingSync
{
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Syncing...";
}
-(void)contentEndingSync
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
-(void)Sync
{
[AppDelegate.appContentManager downloadContent];
}
-(void)dismissSelf
{
if (![AppDelegate.appContentManager subsetArrayFromFileArrayWithNonVidContentThatShouldDisplay] && ![AppDelegate.appContentManager subsetArrayFromFileArrayWithVideoContentThatShouldDisplay]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No Files to Display"
message:#"Please update or enable content"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
return;
}
else if ([AppDelegate.appContentManager subsetArrayFromFileArrayWithNonVidContentThatShouldDisplay])
{
[self dismissViewControllerAnimated:YES completion:Nil];
}
else
{
[self performSegueWithIdentifier:#"goToVideos" sender:self];
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"goToVideos"])
{
((BIDViewController *)segue.destinationViewController).stackedByManager = TRUE;
}
}
#end
It's not necessary to reloaddata on orientation change but if you want to reload table try to call [myTable reloadData] after the completion of the background download and only if the orientation has changed (you can set a bool for this on orient. change).
I'm trying to create a simple modal view controller that lets you edit text using a text view. However, when I present the view controller modally, it slides in from the bottom left direction instead of just sliding in from the bottom.
Here's a video of the weird effect: http://youtu.be/9M_MHA5mt1M
My controller simply watches for the keyboard to show and then resizes the text view using auto layout appropriately. Here's the code:
#import "TextPicker.h"
#interface TextPicker ()
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeight;
#end
#implementation TextPicker
- (id)initWithText:(NSString *)text
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
self = [storyboard instantiateViewControllerWithIdentifier:#"textPicker"];
if (self) {
self.text = text;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self observeKeyboard];
//self.textView.text = self.text;
[self.textView becomeFirstResponder];
}
- (void) viewWillDisappear:(BOOL)animated {
[self.textView resignFirstResponder];
}
- (IBAction)savePressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)cancelPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void) dealloc {
[self stopObervingKeyboard];
}
- (void)observeKeyboard {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)stopObervingKeyboard {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardFrame = [kbFrame CGRectValue];
self.keyboardHeight.constant = -keyboardFrame.size.height;
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
}
- (void)keyboardWillHide:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
self.keyboardHeight.constant = 0;
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
}
- (IBAction)dismissKeyboard:(id)sender {
[self.textView resignFirstResponder];
}
#end
Your view is animating as you have asked it to by wrapping the [self.view layoutIfNeeded] call inside an animation block.
In viewDidLoad you begin observing keyboard changes, and when you detect them you animate the adjustments, this is normally correct. But then, before the view does its first layout, you show the keyboard; this results in an animation for all the views from CGRectZero to their proper sizes. And this is the effect you are seeing.
So basically you need to give the view a chance to layout before your animated layoutIfNeeded call. Probably the easiest way to do this is simply to move [self.textView becomeFirstResponder]; to either viewWillAppear: or viewDidAppear:.
*As a side note, remember to call super in appearance calls. I noticed you did not call [super viewWillDisappear];.