NSInvalidArgumentException Custom Button with NSNotificationCenter - ios

I am trying to create a custom button and add its class as an observer to NSNotificationCenter in the custom initializer like so.
#implementation SACenterDiskButton
-(id)initWithImage:(UIImage *)image forView:(UIView *)view;
{
self = [super init];
if (self) {
self = [UIButton buttonWithType:UIButtonTypeCustom];
self.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
double buttonWidth = view.frame.size.width/5;
self.frame = CGRectMake(0.0, 0.0, buttonWidth, 57);
self.adjustsImageWhenHighlighted = NO;
self.imageView.clipsToBounds = NO;
self.imageView.contentMode = UIViewContentModeCenter;
[self setImage:image forState:UIControlStateNormal];
[self setBackgroundColor:[UIColor colorWithRed:0.317 green:0.4 blue:0.544 alpha:1]];
[view addSubview:self];
//Add observers for when tracks are played/paused/ended for spinning the center button
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(trackPlayedNotification:) name:#"trackPlayed" object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(playPauseNotification:) name:#"togglePlayPause" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:#"songEnded" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
return self;
}
#pragma mark - Notificaton Center
-(void)applicationWillEnterForeground:(NSNotification *)notification
{
BOOL playerIsPlaying = [PlayerManager sharedManager].playerIsPlaying;
if (playerIsPlaying) {
[self.imageView rotate360WithDuration];
}
}
-(void)itemDidFinishPlaying:(NSNotification *)notification
{
[self.imageView stopAllAnimations];
}
-(void)trackPlayedNotification:(NSNotification *)notification
{
[self.imageView rotate360WithDuration];
}
-(void)playPauseNotification:(NSNotification *)notification
{
NSNumber *playerIsPlaying = notification.userInfo[#"playerIsPlaying"];
if ([playerIsPlaying boolValue]) {
[self.imageView pauseAnimations];
}
else {
[self.imageView resumeAnimations];
}
}
The button is working well except for when a notification is sent to it, I get this error.
-[UIButton applicationWillEnterForeground:]: unrecognized selector sent to instance 0x17dd8930
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButton trackPlayedNotificaion:]: unrecognized selector sent to instance 0x17dd8930'
*** First throw call stack:
Am I adding observers to my class properly? Any ideas why I am getting this crash?

The problem is the following line:
self = [UIButton buttonWithType:UIButtonTypeCustom];
This reassigns self to a new UIButton instance, replacing the desired new instance of your custom button class.
Simply remove that line and your code will work as expected.

Related

Change tab on custom UIView

I basically just need to change tabs when the user taps a button on my custom UIView
This is my UIView implementation
#implementation CustomMenuView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIButton *searchButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 135.0, 40.0)];
searchButton.backgroundColor = [UIColor clearColor];
[searchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
searchButton.titleLabel.font = [UIFont systemFontOfSize:15];
[searchButton setTitle:#"Search" forState:UIControlStateNormal];
searchButton.tag = 0;
[searchButton addTarget:self action:#selector(menuItemTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:searchButton];
}
return self;
}
-(void)menuItemTapped:(UIButton*)sender{
self.tabBarController.selectedIndex = sender.tag;
}
And my ViewController class:
UIView *menuView;
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat height = [UIScreen mainScreen].bounds.size.height;
menuView = [[CustomMenuView alloc]initWithFrame:CGRectMake(0, 60, 135, height)];
[self.view addSubview:menuView];
}
This crashes because the UIView does not have a reference to the tabBarController. How do I call a method on my custom view's parent or what is the best approach to solve this?
To the best of my knowledge, you could use the delegate pattern here. So you create a protocol named CustomMenuViewDelegate and declare a weak property on CustomMenuView of this type. When the menuItemTapped: method is called you call a method on the CustomMenuViewDelegate property. You can make your ViewController conform to the delegate protocol and set is as the delegate in the viewDidLoad method.
I ended up deciding to use local notifications like this:
On my custom UIView
-(void)menuItemTapped:(UIButton*)sender{
NSDictionary* userInfo = #{#"tab": #(sender.tag)};
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"MenuItemSelected" object:self userInfo:userInfo];
UIViewController class
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveMenuItemSelectedNotification:)
name:#"MenuItemSelected"
object:nil];
}
-(void) receiveMenuItemSelectedNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:#"MenuItemSelected"])
{
NSDictionary* userInfo = notification.userInfo;
NSNumber* tab = (NSNumber*)userInfo[#"tab"];
NSLog (#"Successfully received test notification! %i", tab.intValue);
self.tabBarController.selectedIndex = tab.intValue;
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MenuItemSelected" object:nil];
}

iOS Methods get the call but does not change color of UIVIew

I am trying to change the color of UIView to indicate network connection. In my methods turnIndicatorViewOff and turnIndicatorViewOn, I am changing the color of indicatorView to red and green respectively. And It's not showing the color change. Those methods get the call. I tried showing an alert and it works fine too but I can't hide or change the color of indicatorView. Also it's not nil. What could be wrong?
My code:
- (void) viewDidLoad
{
indicatorView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, VIEW_WIDTH, 2)];
indicatorView.backgroundColor = [UIColor greenColor];
[self.view addSubview:indicatorView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(pubNubConnectionNotification:) name:#"MQTT_CONNECTION_NOTIFICATION" object:nil];
}
- (void) pubNubConnectionNotification: (NSNotification *) notification
{
if([[notification object] boolValue])
{
[self turnIndicatorViewOn];
}
else
{
[self turnIndicatorViewOff];
}
}
- (void) turnIndicatorViewOff
{
indicatorView.backgroundColor = [UIColor redColor];
}
- (void) turnIndicatorViewOn
{
indicatorView.backgroundColor = [UIColor greenColor];
}

ADInterstitialAd causing memory issues

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.

How can I darken the ECSlidingViewController view controller in the transition?

The documentation mentions adding shadows to the controller being animated to show the slide menu. However, instead of a shadow I would like to make the animated view controller to be darker. Is this possible?
I simply created a masking view and presented/removed it on notifications from the slidingviewcontroller. Added some nice fade in and fade out action for effect :) Hope this helps
#import <UIKit/UIKit.h>
UIView *overLayView;
#interface MyViewController : UIViewController {
UIView *overLayView;
}
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
overLayView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
overLayView.backgroundColor = [UIColor blackColor];
}
-(void) viewWillAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(disableView) name:ECSlidingViewUnderLeftWillAppear object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enableView) name:ECSlidingViewTopWillReset object:nil];
}
- (void)viewWillDisappear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] removeObserver:self name:ECSlidingViewUnderLeftWillAppear object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:ECSlidingViewTopWillReset object:nil];
}
-(void) disableView {
overLayView.alpha = 0;
[self.view addSubview:overLayView];
[UIView animateWithDuration:0.5 animations:^{
overLayView.alpha += kViewHelperUIViewMaskAlpha;
}];
}
-(void) enableView {
[UIView animateWithDuration:0.5 animations:^{
overLayView.alpha -= kViewHelperUIViewMaskAlpha;
} completion:^(BOOL fin){
if(fin){
[overLayView removeFromSuperview];
}
}];
}
#end

NSNotificationCenter doesn't work

I do notifications like this: I have code in mainView:
- (void)viewDidLoad
{
CGRect view1Hrame;
view1Hrame.origin.x = 160;
view1Hrame.origin.y = 215;
view1Hrame.size = self.currenWeatherView.frame.size;
weatherViewController *view1 = [[weatherViewController alloc] initWithForecast:[[Forecast alloc] initWithForecastInCurrentLocation]];
[[NSNotificationCenter defaultCenter] addObserver:view1 selector:#selector(forecastChanged) name:#"forecastChanged" object:nil];
view1.view.backgroundColor = [UIColor colorWithRed:0 green:255 blue:0 alpha:0.06];
[currenWeatherView addSubview: view1.view];
}
in Forecast class:
[[NSNotificationCenter defaultCenter] postNotificationName:#"forecastChanged" object:nil];
in weatherViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(forecastChanged) name:#"forecastChanged" object:nil];
// Do any additional setup after loading the view from its nib.
}
It does not work. What did I do wrong?
if you want to call notification in weatherViewController then you need to define this class's viewDidLoad method.
Make sure you are calling viewDidLoad of this viewController.

Resources