NSNotificationCenter not working, how to debug - ios

I followed the dreammlax example, but I can't seem to get my NSNotification to work. Is there a framework or something I need to add? How do I debug the code.
FirstViewController posting
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)btnSend:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification"
object:self];
self.tabBarController.selectedIndex = 1;
}
#end
SecondViewController receiving
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveTestNotification:)
name:#"TestNotification"
object:nil];
//===Removed ===[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void) receiveTestNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"TestNotification"])
NSLog (#"Successfully received the test notification!");
}
#end

Remove [[NSNotificationCenter defaultCenter] removeObserver:self]; after adding observer and it will work
Or you can move [[NSNotificationCenter defaultCenter] removeObserver:self]; to dealloc method
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
For the question why it doesn't work on first time run.
It's because postNotificationName is called before SecondViewController is initialized. To fix it try the below code.
- (IBAction)btnSend:(id)sender {
self.tabBarController.selectedIndex = 1;
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification"
object:self];
}

Related

__NSCFString playbackFinished unrecognized selector from MPMoviePlayerController

Im using MPMoviePlayerController inside a class Ive built called MYVideo. Here is the code:
#import <MediaPlayer/MediaPlayer.h>
#import "MYVideo.h"
#interface MYVideo()
#property (strong, nonatomic) UIView * viewRef;
#property (strong, nonatomic) NSDictionary * contentData;
#property (strong, nonatomic) MPMoviePlayerController * videoController;
#end
#implementation MYVideo
#synthesize contentData,videoController,viewRef;
- (MYVideo*) initIntoView: (UIView*) view withContent:(NSDictionary*)contentDict{
self=[super init];
viewRef=view;
contentData = contentDict;
NSString *rawUrl = [[NSString alloc] initWithFormat:#"http://....com/app/%#.mp4", [contentDict objectForKey:#"cnid"]];
NSURL *videoUrl = [[NSURL alloc]initWithString:rawUrl];
videoController = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
videoController.movieSourceType=MPMovieSourceTypeFile;
videoController.view.frame = viewRef.bounds;
[videoController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
videoController.controlStyle=MPMovieControlStyleNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playbackFinished:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:videoController];
[viewRef addSubview:videoController.view];
return self;
}
- (void) playbackFinished: (NSNotification*) notification {
NSLog(#"playback finished");
if(videoController){
[videoController play];
}
}
- (void) play: (int) offset {
videoController.initialPlaybackTime=offset;
[videoController play];
}
- (void) stop {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"playbackFinished"
object:nil];
if(videoController){
[videoController stop];
}
}
- (void) destroy {
if(videoController){
[videoController stop];
[videoController.view removeFromSuperview];
}
}
#end
My problem is that occasionally I get the following error:
playback finished
-[__NSCFString playbackFinished:]: unrecognized selector sent to instance 0x1664e6a0
Which I'm guessing is caused by the MPMoviePlayerController firing off the "playbackFinished" notification when this video class has already been released. Am I right in thinking this?
Thing is, this MYVideo class should still be there while the video is playing, this error only occurs when the video is playing and in the console log my NSLogging of "playback finished" immeditately preceeds the crash. Also I never shut down the class without first removing the "playbackFinished" observer.
Can anybody suggest to me why I would be getting this crash?
Many thanks.
Yeah it looks like you are not removing the observer, as this code:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"playbackFinished"
object:nil];
should be:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"playbackFinished:"
object:nil]; // ^
or better still (as you don't really care what's being called):
[[NSNotificationCenter defaultCenter] removeObserver:self
name:nil
object:videoController];
Also given you use tests like if (videoController) { ... } in so many places, you need to ensure it goes nil ASAP:
- (void)destroy {
if(videoController){
[videoController stop];
[videoController.view removeFromSuperview];
videoController = nil; // Add
}
}

After calling Reachability startNotifier, the Network status changes to 0

in my iOS application, I need to use Reachability to complete the following tasks:
When a view controller is rendered, the application needs to check the connection status, and display the connection icon image accordingly (online or offline).
When the user stays on the view controller, and if the internet connection status changes, the application will be notified with the change, and then do some tasks accordingly.
I used the Reachability class which is produced by Tony Million in my application, but a strange thing happened:
After calling the startNotified method, the reachability status changes to 0(NotReachable).
The following is my code:
BTLandingViewController.h
#import <UIKit/UIKit.h>
#import "MBProgressHUD.h"
#import "Reachability.h"
#interface BTLandingViewController : UIViewController <UIAlertViewDelegate>
{
__weak IBOutlet UIImageView *internetIndicator;
MBProgressHUD *hud;
Reachability *reach;
UIAlertView *emptyRecordsAlert;
UIAlertView *syncReminderAlert;
}
#end
Part of the code in
BTLandingViewController.m
#import "BTLandingViewController.h"
#interface BTLandingViewController ()
#end
#implementation BTLandingViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialize reachability
reach = [Reachability reachabilityWithHostname:BTReachTestURL];
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Register with reachability changed notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
NetworkStatus internetStatus = [reach currentReachabilityStatus]; // value = 1 or 2
***// PS: If there is connection, before [reach startNotifier], the internetStatus's value reflects the connection status (1 or 2)***
// Start notify reachability change
[reach startNotifier];
internetStatus = [reach currentReachabilityStatus]; // Value = 0;
***// PS: Even if there is connection, after [reach startNotifier], the internetStatus's value becomes 0***
}
Thank you for your help.
I have finally solved this problem by working around the startNotifier trap I mentioned above. Calling startNotifier does change the network status to 0, but I declare a variable to store the initial network status when the view controller is rendered. And then use the value of the variable in a if statement in the method which handles the action triggered by reachabilityChanged notification. The following is my code:
BTLandingViewController.h
#import <UIKit/UIKit.h>
#import "MBProgressHUD.h"
#import "Reachability.h"
#interface BTLandingViewController : UIViewController <UIAlertViewDelegate>
{
__weak IBOutlet UIImageView *internetIndicator;
MBProgressHUD *hud;
NSInteger initialNetworkStatus;
UIAlertView *emptyRecordsAlert;
UIAlertView *syncReminderAlert;
}
#end
BTLandingViewController.m
#import "BTLandingViewController.h"
#import "BTSyncEngine.h"
#import "BTInstructorLoginViewController.h"
#import "BTGlobalParams.h"
#interface BTLandingViewController ()
#end
#implementation BTLandingViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewDidUnload {
internetIndicator = nil;
[super viewDidUnload];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Register with sync complete notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(syncCompleted:) name:#"BTSyncEngineSyncCompleted" object:nil];
// Register with reachability changed notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
Reachability *reach = [Reachability reachabilityWithHostname:BTReachTestURL];
reach.reachableBlock = ^(Reachability * reachability){
dispatch_async(dispatch_get_main_queue(), ^{
initialNetworkStatus = 1;
[internetIndicator setImage:[UIImage imageNamed:#"online_indicator.png"]];
// Start syncing local records with the remote server
[[BTSyncEngine sharedEngine] startSync];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Sync in progress";
});
};
reach.unreachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
initialNetworkStatus = 0;
[internetIndicator setImage:[UIImage imageNamed:#"offline_indicator.png"]];
// If the initial sync has been executed
// then use the cached records on the device
if ([[BTSyncEngine sharedEngine] initialSyncComplete]) {
// Go to instructor login screen
BTInstructorLoginViewController *instructorLoginViewController = [[BTInstructorLoginViewController alloc] init];
[instructorLoginViewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:instructorLoginViewController animated:YES completion:nil];
}
// If the initial sync has not been executed
// show an alert view
else {
emptyRecordsAlert = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"Please connect to the internet to load data for the first time use." delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[emptyRecordsAlert show];
}
});
};
[reach startNotifier];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// Unregister with reachability changed notification
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
// Unregister with sync complete notification
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"BTSyncEngineSyncCompleted" object:nil];
}
- (void)syncCompleted:(NSNotification *)note
{
// Update the global sync completed flag
[[BTGlobalParams sharedParams] setSyncCompleted:YES];
// Hide syncing indicator
[MBProgressHUD hideHUDForView:self.view animated:YES];
// Go to instructor login screen
BTInstructorLoginViewController *instructorLoginViewController = [[BTInstructorLoginViewController alloc] init];
[instructorLoginViewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:instructorLoginViewController animated:YES completion:nil];
}
- (void)reachabilityChanged:(NSNotification *)note
{
Reachability *reach = [note object];
if ([reach isReachable] && !initialNetworkStatus) {
[internetIndicator setImage:[UIImage imageNamed:#"online_indicator.png"]];
// Start syncing local records with the remote server
[[BTSyncEngine sharedEngine] startSync];
hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Sync in progress";
}
}
#end

UITextField textInputView message sent to deallocated instance

I have crashes which occur when I tap on UITextField. I have two controllers, let's say A and B. When I show controller B modally in controller A, and tap on UITextField everything is OK. Then I dismiss controller B, and present it again. This time when I click on UITextField crash occurs. When I enabled NSZombie in my scheme, there is message of crash - -[UITextField textInputView]: message sent to deallocated instance.
I have no UITextFieldDelegate set on UITextField. When I tried to debug with Instruments -> Zombies, there is no source code (Unavailable) when I double click some release/retain history line.
My scheme's Build Configuration is Debug. Xcode version is 7.2.1. Crashes occur only on iOS 8. iOS 9 is fine.
PS: When I try re-symbolicate app, I see that there is one missing System Frameworks dyld. Then I press Locate button and open dsym file of my debug app, but error occurs with message "The specified path didn't locate a dSYM for any of the selected libraries."
UPDATE 1:
A controller:
#import <UIKit/UIKit.h>
#interface A : UIViewController
#end
#import "A.h"
#import "RoundedButton.h"
#interface A ()
#property (weak, nonatomic) IBOutlet RoundedButton *signInButton;
#end
#implementation A
- (void)viewDidLoad {
[super viewDidLoad];
[self configUI];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"signIn"]) {
}
}
#pragma mark - Config UI
- (void)configUI {
// setup ui
}
- (void)dealloc {
NSLog(#"%# deallocated", NSStringFromClass([self class]));
}
#end
B controller:
#import <UIKit/UIKit.h>
#interface B : UIViewController
#end
#import "B.h"
#import "SKFormTextField.h"
#import "RoundedButton.h"
#interface B ()
#property (weak, nonatomic) IBOutlet UITextField *textField;
#property (weak, nonatomic) IBOutlet RoundedButton *signInButton;
#end
#implementation B {
SKAlertView *alertView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self configUI];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
#pragma mark - Actions
- (IBAction)cancelSignIn:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - Config UI
- (void)configUI {
self.navigationController.navigationBarHidden = YES;
self.textField.secureTextEntry = true;
self.textField.autocorrectionType = UITextAutocorrectionTypeNo;
}
- (void)dealloc {
NSLog(#"%# deallocated", NSStringFromClass([self class]));
}
#end
UPDATE 2:
When I call UITextField, like self.textField.text = #"Hello, World!";, there is no crash. Only when I pan/touch UITextField or call [self.textField becomeFirstResponder]; it crashes.
Finally, I found reason of my crashes. I have UITextField extension called UITextField (AutoSuggestion) where I called dealloc method to remove own observers.
Code sample:
/* .h file */
#import <UIKit/UIKit.h>
// some protocol
#interface UITextField (AutoSuggestion)
// some properties and methods
#end
/* .m file */
#import "UITextField+AutoSuggestion.h"
#import <objc/runtime.h>
#implementation UITextField (AutoSuggestion)
- (void)observeTextFieldChanges {
self.layer.masksToBounds = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(toggleAutoSuggestion:) name:UITextFieldTextDidChangeNotification object:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideAutoSuggestion) name:UITextFieldTextDidEndEditingNotification object:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(getKeyboardHeight:)
name:UIKeyboardDidShowNotification
object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
}
// other methods
#end
After I removed dealloc method crashes disappeared. Therefore I decided to find new way to implement autosuggestion feature.
The interesting thing is that I didn't import header file in code anywhere, but crashes occurred.

How do I resume the music when I open the app again using MPMusicPlayerController

I have a button called music and when I click on it the MPMediaPickerController pops up and choose which song to play. It plays fine in the app but when I exit the app and go into it again the music stops. Any idea?
ViewController.h:
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController <MPMediaPickerControllerDelegate>
{
MPMusicPlayerController *musicPlayer;
IBOutlet UIButton *music;
IBOutlet UIButton *stopmusic;
}
- (IBAction)music:(id)sender;
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (IBAction)stopmusic:(id)sender
{
[musicPlayer stop];
}
- (IBAction)music:(id)sender
{
MPMediaPickerController *musicSelect;
musicSelect = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
musicSelect.delegate = self;
[self presentViewController:musicSelect animated:YES completion: nil];
}
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker
didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
musicPlayer = [MPMusicPlayerController applicationMusicPlayer];
[musicPlayer setQueueWithItemCollection:(MPMediaItemCollection *)mediaItemCollection];
[self dismissViewControllerAnimated:YES completion:nil];
[musicPlayer play];
}
- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
In your viewDidLoad method register a notification for when app enters foreground:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notification:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
Then have a method that gets executed whenever app enter foreground again. Use the instance property to replay the music again.
- (void)notification:(id)sender {
NSLog(#"Notification: %#", sender);
// add code to play again
}

Unable to receive notification

I try to send local notification. Here some code for class sending the notification:
#interface Sender : UIView
{
NSInteger itemID;
}
#implementation Sender
-(void) changedProperty
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationName" object:NULL];
}
And here the code to receive this notification:
#interface Listener : UIViewController
{
}
#implementation Listener
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(selectedItem:) name:#"NotificationName" object:NULL];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationName" object:NULL];
}
-(void) selectedItem:(NSNotification *) notification
{
// some actions
}
But this code doesn't work. Debugging I see how postNotificationName: object works but the method selectedItem: doesn't call
UPDATE.
Here is more code. Maybe this will help.
extern const NSString* selectItemNotificationName;
#interface vRoomSelectorItem : UIView
{
RoomSelectorItemBackground backgroundType;
NSInteger itemID;
}
#property NSInteger itemID;
-(void) setBackgroundType:(RoomSelectorItemBackground) backgroundType;
#interface vRoomSelectorItem ()
#property RoomSelectorItemBackground backgroundType;
#end
#implementation vRoomSelectorItem
const NSString* selectItemNotificationName = #"Reservation.SelectRoom";
-(RoomSelectorItemBackground) backgroundType
{
return backgroundType;
}
-(void) setBackgroundType:(RoomSelectorItemBackground)value
{
if(backgroundType != value)
{
backgroundType = value;
[self changedBackgroundType];
}
}
-(void) changedBackgroundType
{
if(backgroundType == RoomSelectorItemFilled)
{
// animation
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:(NSString*)selectItemNotificationName object:NULL userInfo:[[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInteger:itemID], #"ID", NULL]];
});
}
else
// reverse animation
}
#import "vRoomSelectorItem.h"
#interface vcReservationSelectRoom : UIViewController
{
NSMutableArray* arraySelectorItems;
}
#implementation vcReservationSelectRoom
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(selectedItem:) name:(NSString*)selectItemNotificationName object:NULL];
for(NSInteger i = 1; i <= SELECTOR_ITEM_COUNT; ++i)
{
vRoomSelectorItem* newItem = [[vRoomSelectorItem alloc] initWithFrame:CGRectMake(/*coordinates*/)];
[self.view addSubview:newItem];
[newItem setBackgroundType:RoomSelectorItemTransparent];
[newItem setItemID:i];
[arraySelectorItems addObject:newItem];
newItem = NULL;
}
}
-(void) selectedItem:(NSNotification *) notification
{
// some actions
}
-(void) dealloc
{
arraySelectorItems = NULL;
[[NSNotificationCenter defaultCenter] removeObserver:self name:(NSString*)selectItemNotificationName object:NULL];
}
#end
From your code in the quetion I think you can try this:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(selectedItem:) name:#"NotificationName" object:NULL];
[self.view changedProperty]; // method of your Sender class
}
It seems like your code should work. Make sure the notification is on the main thread and the workflow is as follows:
Add listener to notification center
Initiate sender
Send notification
You can be sure that it is on the main thread with:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] post...];
});
Couple things I would change:
remove yourself from NSNotificationCenter in -(void)dealloc instead
of -(void)viewDidUnload. viewDidUnload will be deprecated, and
dealloc could get called without viewDidUnload.
For Notifications, I like to store the name in an external constant
to make sure that I don't typo the string:
//header.h
extern NSString *const notificationName;
//implementation.m
NSString *const notificationName = #"notification";
Ok, I've solved the problem. The piece of code above is not enough to understand, why it has not been working. The object of class vcReservationSelectRoom was temporary and it had been destroyed before sending notification from any vRoomSelectorItem. Sorry for my mistake of not showing enough code to solve this problem. And thank you all for helping.

Resources