i'm new to Cocoa and I'm having a little trouble with a sample app i'm writing :
#implementation DeviceDetection
- (id) init {
self = [super init];
if (self) {
notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notCenter addObserver:self
selector:#selector(discMounted:)
name:#"NSWorkspaceDidMountNotification"
object:[NSWorkspace sharedWorkspace]]; // Register for all notifications
}
return self;
}
- (void)discMounted:(NSNotification *)notification
{
NSLog(#"COUCOU");
}
#end
#import <Foundation/Foundation.h>
#interface DeviceDetection : NSObject {
NSNotificationCenter *notCenter;
}
- (void) discMounted:(NSNotification *)notification;
#end
#implementation AppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
DeviceDetection* d = [[DeviceDetection alloc] init];
[d value];
}
#end
With that piece of code i'm getting a the following error when I plug-in a USB drive :
[NSRunLoop discMounted:]: unrecognized selector sent to instance 0x10054c5a0
Any reason why ?
Thx
You need to define dealloc method of DeviceDetection-
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
EDIT 1 -
DrivesOnDock[5207:707] -[DeviceDetection value]: unrecognized selector sent to instance 0x100475b40 when the app starts.
Above error occurs because you haven't defined value in DeviceDetection class.
Related
I'm attempting to use NSKeyedArchiver / NSKeyedUnarchiver in a trivial test case and observing that when initWithCoder: is called, the object that is created reports that it has the wrong class.
TestCoder.h
#import <Foundation/Foundation.h>
#interface TestCoder : NSObject <NSCoding>
#end
TestCoder.m
#import "TestCoder.h"
#implementation TestCoder
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
NSLog(#"TestCoder is a kind of of TestCoder? %#", [self isKindOfClass:TestCoder.class] ? #"YES" : #"NO");
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:#"Test"];
}
#end
NSLog outputs:
2015-07-22 14:10:45.394 TestCoder[10858:632647] TestCoder is a kind of of TestCoder? YES
When I unit test this, however, it doesn't seem to work.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "TestCoder.h"
#interface TestCoderTests : XCTestCase
#end
#implementation TestCoderTests
- (void)testExample {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[TestCoder new]];
TestCoder *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
XCTAssert([object isKindOfClass:TestCoder.class]);
}
#end
My test case reports:
error: -[TestCoderTests testExample] : (([object isKindOfClass:TestCoder.class]) is true) failed
But when I inspect the object's class using the debugger I get results that contradict my test case:
(lldb) po [object isKindOfClass:TestCoder.class]
true
What is going on here?
I can see that I'm not misusing isKindOfClass by using a simple test case that passes.
- (void)testClassEquality {
TestCoder *object = [TestCoder new];
XCTAssert([object isKindOfClass:TestCoder.class]);
}
Is there some trick to get NSCoder to work properly that I can't find in the documentation?
I'm trying to link two different view controllers in an Xcode 6 storyboard with a NSContainerView so that they can be switched conditionally. Unfortunately this tutorial here isn't of any help since things seem to have changed since in Xcode.
So I have two different view controllers and one of them is loaded into the container view by default but I want to be able to load the second view controller into the container view programmatically. Xcode 6 only allows to create embed Segues when I drag from one to the other so that's not much of a help.
Can somebody tell me how this is achieved with Xcode 6?
First, here's a sample GitHub project of the solution: click. I wasn't sure whether you wanted to swap the views or simply push the 2nd view onto a proverbial stack, so I went with a push/pop scheme. If you want to swap the views instead, you should be able to do that fairly easily by just skipping the stack storage.
Essentially, we have our "host" NSViewController that holds a Container View (CV) inside of it. This host doesn't actually manually manage the view controller that the CV is showing at the moment. The way this is done is through, well, a sort of nested view controller that then manages all the other view controllers that you're going to show/hide/push/pop/swap/etc. (Note: you might be able to remove the layering a bit, but in iOS terms, I'm treating the 'Sub View Controller Manager' in the storyboard screenshot sort of like a UINavigationController).
We also take advantage of some custom segues/segue animators in order to be able to do more work in the storyboard.
You just have to tell the content view manager view controller to manipulate its subviews in such a way that the old views that you want to pop "back" to are retained (in this case, using an NSMutableArray) and such that the new views have the right frame or have their constraints set up properly.
Here is a screenshot of the storyboard: Each segue you see on the storyboard of a custom type (looks like this -> { } ) is of type SegueBetweenEmbedded in the sample project. Buttons that push perform a segue, and buttons labeled 'Pop' perform dismissController: on the NSViewController (so that was done in the storyboard).
Here's some code (and there's a lot of it, so I suggest looking at the sample project instead):
ViewController.h
#import <Cocoa/Cocoa.h>
#import "ContentManagerViewController.h"
#class ContentManagerViewController;
#protocol ContentManagerViewControllerHolder <NSObject>
-(ContentManagerViewController*)retreiveContentManagerController;
#end
#interface ViewController : NSViewController <ContentManagerViewControllerHolder>
#end
ViewController.m
#import "ViewController.h"
#import "ContentManagerViewController.h"
#import "BackForwardViewController.h"
#interface ViewController ()
#property ContentManagerViewController *vcController;
-(IBAction)pushViewController:(id)sender;
-(IBAction)popViewController:(id)sender;
-(IBAction)popToRootViewController:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([[segue destinationController] class] == [ContentManagerViewController class]) {
self.vcController = segue.destinationController;
}
}
-(ContentManagerViewController*)retreiveContentManagerController {
return self.vcController;
}
-(IBAction)pushViewController:(id)sender {
// note: this works, but then pop is broken via dismissController: since it wasn't done with a segue.
// Better way is to rig up a manual segue and execute the segue.
//BackForwardViewController *viewController = [[NSStoryboard storyboardWithName:#"Main" bundle:nil] instantiateControllerWithIdentifier:#"BackForwardStoryboardID"];
//[self.vcController push:viewController];
[self performSegueWithIdentifier:#"CustomSegueToBackForward" sender:self];
}
-(IBAction)popViewController:(id)sender {
[self.vcController pop];
}
-(IBAction)popToRootViewController:(id)sender {
[self.vcController popToRoot];
}
#end
SegueBetweenEmbedded.h
#import <Cocoa/Cocoa.h>
#interface SegueBetweenEmbedded : NSStoryboardSegue
#end
SegueBetweenEmbedded.m (sorry not sorry for the nested class)
#import "SegueBetweenEmbedded.h"
#import "ContentManagerViewController.h"
#import "ViewController.h"
#interface SegueAnimator : NSObject <NSViewControllerPresentationAnimator>
- (void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController;
- (void)animateDismissalOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController;
#end
#implementation SegueAnimator
- (void)animatePresentationOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
NSViewController *parent = [fromViewController parentViewController];
if (parent && [parent class] == [ContentManagerViewController class]) {
ContentManagerViewController *manager = (ContentManagerViewController*)parent;
[manager push:viewController];
}
else if ([fromViewController conformsToProtocol:#protocol(ContentManagerViewControllerHolder)]) {
id<ContentManagerViewControllerHolder> holder = (id<ContentManagerViewControllerHolder>)fromViewController;
[[holder retreiveContentManagerController] push:viewController];
}
}
- (void)animateDismissalOfViewController:(NSViewController *)viewController fromViewController:(NSViewController *)fromViewController {
NSViewController *parent = [viewController parentViewController];
if ([parent class] == [ContentManagerViewController class]) {
ContentManagerViewController *manager = (ContentManagerViewController*)parent;
[manager pop];
}
}
#end
#implementation SegueBetweenEmbedded
- (void)perform {
SegueAnimator *animator = [[SegueAnimator alloc] init];
[self.sourceController presentViewController:self.destinationController
animator:(id<NSViewControllerPresentationAnimator>)animator];
}
#end
ContentManagerViewController.h
#import <Cocoa/Cocoa.h>
#interface ContentManagerViewController : NSViewController
-(void)push:(NSViewController*)viewController;
-(void)pop;
-(void)popToRoot;
#end
ContentManagerViewController.m
#import "ContentManagerViewController.h"
#import "BackForwardViewController.h"
#interface ContentManagerViewController ()
#property (weak) IBOutlet NSView *subViewControllerManager;
#property NSViewController *currentViewController;
#property NSMutableArray<NSViewController*> *viewControllerStack;
#end
#implementation ContentManagerViewController
-(instancetype)init {
self = [super init];
self.viewControllerStack = [NSMutableArray array];
return self;
}
-(instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
self.viewControllerStack = [NSMutableArray array];
return self;
}
-(instancetype)initWithNibName:(NSNibName)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
self.viewControllerStack = [NSMutableArray array];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)showViewController:(NSViewController*)viewController {
[self addChildViewController:viewController];
viewController.view.frame = self.currentViewController.view.frame;
[self.view addSubview:viewController.view];
self.currentViewController = viewController;
}
-(void)removeCurrentViewControllerFromView {
[self.currentViewController.view removeFromSuperview];
[self.currentViewController removeFromParentViewController];
}
-(void)push:(NSViewController*)viewController {
[self removeCurrentViewControllerFromView];
[self.viewControllerStack addObject:viewController];
[self showViewController:viewController];
}
-(void)pop {
if (self.viewControllerStack.count > 1) {
[self removeCurrentViewControllerFromView];
[self.viewControllerStack removeLastObject];
NSViewController *viewController = [self.viewControllerStack lastObject];
[self showViewController:viewController];
}
}
-(void)popToRoot {
while (self.viewControllerStack.count > 1) {
[self pop];
}
}
-(void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
// this will be called on the initial embed to set up the first view controller
self.currentViewController = segue.destinationController;
[self.viewControllerStack addObject:segue.destinationController];
}
#end
BackForwardViewController.h
#import <Cocoa/Cocoa.h>
#interface BackForwardViewController : NSViewController
#end
BackForwardViewController.m
#import "BackForwardViewController.h"
#interface BackForwardViewController ()
#end
#implementation BackForwardViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
}
#end
I am having problems with a map view in my app. I have created a button that when clicked should show users location on the map, but nothing happens (no error messages occur).
I believe the issue may lie in the way I've written the delegates. The code from the relevant .h and .m files is below:
mapViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#interface mapViewController : UIViewController {
MKMapView *mapview;
}
#property (nonatomic, retain) IBOutlet MKMapView *mapview;
-(IBAction)setMap:(id)sender;
-(IBAction)getCurrentLocation:(id)sender;
#property (nonatomic, retain) IBOutlet CLLocationManager *locationManager;
#end
mapViewController.m
#import "mapViewController.h"
#interface mapViewController ()
#end
#implementation mapViewController {
CLLocationManager *locationManager;
}
#synthesize mapview;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.locationManager.delegate=self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.requestWhenInUseAuthorization;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)setMap:(id)sender {
switch (((UISegmentedControl *) sender).selectedSegmentIndex) {
case 0:
mapview.mapType = MKMapTypeStandard;
break;
case 1:
mapview.mapType = MKMapTypeSatellite;
break;
case 2:
mapview.mapType = MKMapTypeHybrid;
break;
default:
break;
}
}
-(IBAction)getCurrentLocation:(id)sender {
mapview.showsUserLocation = YES;
}
#end
Any help would be greatly appreciated, thanks
You have to implement the MapKit delegates. (Make sure you add the delegate signature in .h of your view controller)
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta = 0.005;
span.longitudeDelta = 0.005;
CLLocationCoordinate2D location;
location.latitude = userLocation.coordinate.latitude;
location.longitude = userLocation.coordinate.longitude;
region.span = span;
region.center = location;
[mapView setRegion:region animated:YES];
}
Extra: To handle App is on foreground or not:
We add 2 event observers to observe the App is entering background / returning active:
- (void)viewDidLoad{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appToBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appReturnsActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)appToBackground{
[mapview setShowsUserLocation:NO];
}
- (void)appReturnsActive{
[mapview setShowsUserLocation:YES];
}
I've been having problems calling on a block, I keep getting the error EXC_BAD_ACCESS code=2.
.h file
#interface TheClass : UIView
typedef void (^AnotherBlock)();
#property (copy) AnotherBlock block;
- (void) testMethod;
#end
.m file
#import "TheClass.h"
#implementation TheClass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self testMethod];
}
return self;
}
- (void) testMethod {
self.block();
}
#end
And in the main view controller
.m file
#import "MainViewController.h"
#import "TheClass.h"
#interface MainViewController ()
#property TheClass *mClass;
#end
#implementation MainViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)button:(id)sender {
self.mClass = [[TheClass alloc] init];
[self.mClass testMethod];
self.mClass.block = ^ {
NSLog(#"Remote Block Worked");
};
}
#end
The button is linked up and working correctly, when touched I get the error message.
In the debugger there is a message that says:
_block AnotherBlock <parent is NULL>
Any help would be great. Thanks
You are calling testMethod before there's anything assigned to the block variable. Make sure there's something in it before calling testMethod.
I'm a bit perplexed about what I'm trying to accomplish. I have a page view controller that has a data source containing an array list of images. It's actually a tutorial that a user can flip through. What I'm trying to do is make the last page a log in screen so the user can enter info and hit a login button. I thought this would be as simple as adding a login view controller to the array but oooh how wrong I was D: When I tried that I got this error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewController _isResizable]: unrecognized selector sent to instance 0xa160660'
I do apologise for being such a noob I'm new to all of this just trying to get my head around it. Here's my code (accomplished by using this site actually):
My data source (ModelController.h)
#import <Foundation/Foundation.h>
#class DataViewController;
#interface ModelController : NSObject <UIPageViewControllerDataSource>
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard;
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;`
#end
ModelController.m
#import "ModelController.h"
#import "DataViewController.h"
#import "LoginViewController.h"
#interface ModelController()
#property (readonly, strong, nonatomic) NSArray *pageData;
#end
#implementation ModelController
- (id)init
{
self = [super init];
if (self)
{
// Create the data model
_pageData = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"tutorial1.png"],
[UIImage imageNamed:#"tutorial2.png"],
[UIImage imageNamed:#"lastWishes.png"],
[UIImage imageNamed:#"todo.png"],
[UIImage imageNamed:#"web.png"],
(LoginViewController*)[[UIViewController alloc] init],
nil];
}
return self;
}
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if (([self.pageData count] == 0) || (index >= [self.pageData count]))
{
return nil;
}
// Create a new view controller and pass suitable data.
DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"DataViewController"];
dataViewController.dataObject = self.pageData[index];
return dataViewController;
}
- (NSUInteger)indexOfViewController:(DataViewController *)viewController
{
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return [self.pageData indexOfObject:viewController.dataObject];
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageData count]) {
return nil;
}
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
#end
The Parent (RootViewController.h)
#import <UIKit/UIKit.h>
#interface RootViewController : UIViewController <UIPageViewControllerDelegate>
#property (strong, nonatomic) UIPageViewController *pageViewController;
#end
RootViewController.m
#import "RootViewController.h"
#import "ModelController.h"
#import "DataViewController.h"
#interface RootViewController ()
#property (readonly, strong, nonatomic) ModelController *modelController;
#end
#implementation RootViewController
#synthesize modelController = _modelController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationVertical options:nil];
self.pageViewController.delegate = self;
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
CGRect pageViewRect = self.view.bounds;
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (ModelController *)modelController
{
// Return the model controller object, creating it if necessary.
// In more complex implementations, the model controller may be passed to the view controller.
if (!_modelController) {
_modelController = [[ModelController alloc] init];
}
return _modelController;
}
#pragma mark - UIPageViewController delegate methods
/*
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating: (BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted: (BOOL)completed
{
}
*/
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here.
UIViewController *currentViewController = self.pageViewController.viewControllers[0];
NSArray *viewControllers = #[currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMin;
}
#end
The Child (DataViewController.h)
#import <UIKit/UIKit.h>
#interface DataViewController : UIViewController
#property (strong, nonatomic) id dataObject;
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
DataViewController.m
#import "DataViewController.h"
#interface DataViewController ()
#end
#implementation DataViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.imageView.image = _dataObject;
}
#end
Once again, the code in question is here where I'm trying to add a view controller to the data source as the last page:
_pageData = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"tutorial1.png"],
[UIImage imageNamed:#"tutorial2.png"],
[UIImage imageNamed:#"lastWishes.png"],
[UIImage imageNamed:#"todo.png"],
[UIImage imageNamed:#"web.png"],
(LoginViewController*)[[UIViewController alloc] init],
nil];
and getting unrecognized selector error when at runtime. I've also tried this as well:
- (id)init
{
self = [super init];
if (self)
{
LoginViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
// Create the data model
_pageData = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"tutorial1.png"],
[UIImage imageNamed:#"tutorial2.png"],
[UIImage imageNamed:#"lastWishes.png"],
[UIImage imageNamed:#"todo.png"],
[UIImage imageNamed:#"web.png"],
viewController,
nil];
}
return self;
}
Any suggestions would be great. THanks!!
Your idea is 100% correct, your implementation is not.
This line:
dataViewController.dataObject = self.pageData[index];
is very suspicious because that will return a UIViewController in the case of your login screen. I would suggest you type-check your page data, if it is already a UIViewController subclass, just return it, if it is (in your case) a UIImage add it as the data object.