I have a very simple project. Extremely watered down. All it does is load some text into an NSTableView. That's it. But it's using a new window and controller, called "Revisions."
As soon as the new window becomes active, it crashes or just locks up. No errors in the console. If it sits in the background, behind the AppDelegate's window, it appears to load the information fine. I can see the table is populated perfectly. But as soon as I click on the window and make it active, it crashes/locks.
This is driving me nuts. I know it has to do with memory management, but I can't figure out where or how or why.
Note, I'm in XCode 4.2, where there's no more releasin' (unless I change some settings, of course).
All connections in
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "Revisions.h"
#implementation AppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
Revisions *rev = [[Revisions alloc] initWithWindowNibName:#"Revisions"];
[rev loadWindow];
}
Revisions.h
#import <Cocoa/Cocoa.h>
#interface Revisions : NSWindowController
{
IBOutlet NSTableView *quicktimesList;
IBOutlet NSTableView *unusedDataList;
}
#end
Revisions.m
#import "Revisions.h"
#implementation Revisions
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
NSLog(#"Creating number of rows.");
return 10;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSLog(#"Starting Loop.");
NSString *words = [[NSString alloc] initWithFormat:#"Row %i", rowIndex];
NSLog(#"Looping %i", (int)rowIndex);
return words;
}
#end
Ok. I'm going to give you a couple of tips when dealing with potential memory leaks in Xcode 4.2.
When writing software for Mac it is advisable to enable garbage collection in your build settings. Just simply search for "garbage collection" in the search bar of your build settings and set it to "required".
If you have memory leaks in your project just press the "product" menu and hit "Analyze".This does as the menu item states, it analyses your project for potential memory leaks and helps you track them down.
Hope this helps!
Related
I have successfully implemented a file-drop functionality in my app. The Application window has a few NSTabView objects where dropping on them does not work. Anywhere else in the window the file-drop works fine.
I have tried to make the app delegate a delegate for the NSTabView, but this did not help.
Anyone have a setup for the NSTabView not to filter out the drop-actions so the whole window can be transparent to the file-drop actions ?
For a more generic solution than olekeh's I made it IB friendly so you can hook it up to any object that complies with the NSDraggingDestination protocol.
#import <Cocoa/Cocoa.h>
#interface DropFilesView : NSView
#property (nullable, assign) IBOutlet id<NSDraggingDestination> dropDelegate;
#end
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender{
return [self.dropDelegate draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
return [self.dropDelegate performDragOperation:sender];
}
#end
I found the solution to this !! - I am posting it here for others who might need.
The NSTabView object has for each of its tabs an NSTabViwItem.
Under each of those, there is a regular NSView - that I subclassed with the following code: - The code assumes that you already have "draggingEntered" and "performDragOperation" in your AppDelegate as this class just forwards these messages to the app delegate. You will also need to put the declarations for those methods in you AppDelegate.h
// DropFilesView.h
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface DropFilesView : NSView
#end
and the implementation:
// DropFilesView.m
#import "DropFilesView.h"
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del performDragOperation:sender];
}
#end
In Interfacebuilder, I set the new class for all the NSView objects covering areas where drop does not work, to this new one.
A similar approach can be used for NSImageView and the WebView classes. However, for the last one, do not use [super awakeFromNib] to prevent the default drag-and drop handling for the web view object.
First I'll give you a short overview.
I'm ...
creating a new cocoa project
customizing the AppDelegate (see listing 1)
adding a "Custom View" to my MainMenu.xib
creating a new Cocoa Class (NSViewController + XIB) in the project, calling it MyTableViewController.*
adding a "Table View" to the recently added ViewController, like described in LINK
the code of my MyTableViewController can be seen in listing 2
Now to my problem.
The table and it's content is shown.
But if I select an item of the table and press a button (connected to (IBAction)action:(id)sender) on this subview, <NSIndexSet: 0x60000022c100>(no indexes) is shown in the output (see: selectedColumnIndexes).
After experimenting a while, I found out that there are two instances of the MyTableViewController class.
Can someone please explain me why there are two instances and help me to fix this problem.
Thx
listing 1:
// FILE: AppDelegate.h
#import <Cocoa/Cocoa.h>
#class MyTableViewController;
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (nonatomic, assign) NSViewController * currentViewController;
#property (nonatomic, strong) MyTableViewController * myTableViewController;
#property (weak) IBOutlet NSView *myview;
#end
// FILE: AppDelegate.m
#import "AppDelegate.h"
#import "MyTableViewController.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self changeViewController];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
return YES;
}
- (void)changeViewController
{
if ([self.currentViewController view] != nil) {
[[self.currentViewController view] removeFromSuperview];
}
switch (0) {
case 0:
default:
if (self.myTableViewController == nil) {
_myTableViewController = [[MyTableViewController alloc] initWithNibName:#"MyTableViewController" bundle:nil];
}
self.currentViewController = self.myTableViewController;
NSLog(#"EndView");
break;
}
[self.myview addSubview:[self.currentViewController view]];
[[self.currentViewController view] setFrame:[self.myview bounds]];
[self.currentViewController setRepresentedObject:[NSNumber numberWithUnsignedInteger:[[[self.currentViewController view] subviews] count]]];
[self didChangeValueForKey:#"viewController"];
NSLog(#"ViewController changed");
}
#end
listing 2:
// FILE: MyTableViewController.h
#import <Cocoa/Cocoa.h>
#interface MyTableViewController : NSViewController
#property (weak) IBOutlet NSTableView *tview;
- (IBAction)action:(id)sender;
#end
// FILE: MyTableViewController.m
#import "MyTableViewController.h"
#import "AppDelegate.h"
#interface MyTableViewController ()
#end
#implementation MyTableViewController
- (NSUInteger)numberOfRowsInTableView:(NSTableView *)tableViewObj {
return 2;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if ([tableView tableColumns][0] == tableColumn) {
return #"bla";
} else if ([tableView tableColumns][1] == tableColumn) {
return #"blub";
}
NSLog(#"dropped through tableColumn identifiers");
return NULL;
}
- (IBAction)action:(id)sender {
// selectedColumnIndexes
NSLog(#"%#", [self.tview selectedColumnIndexes]);
}
#end
Two instances of a class, when you only expect/want one, can be caused by failing to appreciate that objects in your xib files are themselves actual automatically-generated instances of that class.
Have you dragged a blue cube into either of your xib files and set it's class to your view controller subclass? If you have, then this will account for one of the objects that you're seeing - Apple's machinery creates it on your behalf. The second object is the one created by you, in code, in your changeViewController method.
I get the impression that you're simply trying to create a window, which contains an NSTableView, which in turn gets its data from your own NSViewController subclass. Is this correct? If it is then you should do away with the second xib file, and instead just use the xib created for you when you created the project.
In Brief
Drag a blue object cube into the Interface Builder dock and set it's subclass to MyTableViewController using the Identity Inspector.
With your view controller blue-cube selected go to the Connections Inspector and drag from the view option to your table view - you must make sure you're dragging to the table view, not the scroll view or clip view that encloses it.
Select the table view (again, make sure it really is the table view you've selected), and go to it's Connections Inspector. Drag from the data source and delegate options to your blue view-controller cube.
Implement the relevant data-soruce methods
Hint: If you aren't sure which inspector is which, open the right sidebar in Xcode and select a xib file from the left file-viewer sidebar. Sit the cursor over each of the icons at the top of the right sidebar and the tool tip will tell you which is which.
Update
A good way of identifying specific objects, and something that can assist with debugging, is to set their identifier. In the attributes inspector, this is the restoration ID. Do this for your NSTableView instance, and for your NSTableColumn instances. Then, in your data-source methods do some logging with them - for instance does the table view passed as the first argument for this methods have the expected identifier, what about the table columns?
i am a .net programmer and last week i started to read about objective-c. Class related stuff are kinda clear and today i learnt about protocols and delegates, i can't say it is 100% clear but i got it, it looks a lot with delegates and events from c#.
This is a simple example i created following a tutorial. It is all about 2 screens, the first one(a label and a button) launches the second one(a textbox and a button) which sends back a string. I think of it as a classic example of using events, no matter the programming language.
#import <UIKit/UIKit.h>
#import "ValueViewController.h"
#interface ViewController : UIViewController<ValueViewControllerDelegate>
- (IBAction)btnGetValue:(id)sender;
#property (weak, nonatomic) IBOutlet UILabel *lblCurrentValue;
#end
#import "ViewController.h"
#import "ValueViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btnGetValue:(id)sender {
ValueViewController *valueVC = [self.storyboard instantiateViewControllerWithIdentifier:#"ValueViewController"];
valueVC.delegate=self;
[self presentViewController:valueVC animated:FALSE completion:nil];
}
-(void) sendValue:(ValueViewController *)controller didFihishWithValue:(NSString *)value
{
self.lblCurrentValue.text=value;
}
#end
#import <UIKit/UIKit.h>
#class ValueViewController;
#protocol ValueViewControllerDelegate<NSObject>
-(void) sendValue:(ValueViewController*) controller didFihishWithValue:(NSString*) value;
#end
#interface ValueViewController : UIViewController<UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *txtValue;
- (IBAction)btnSetValue:(id)sender;
#property (weak, nonatomic) id<ValueViewControllerDelegate> delegate;
#end
#import "ValueViewController.h"
#interface ValueViewController ()
#end
#implementation ValueViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.txtValue.delegate=self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
return [textField resignFirstResponder];
}
- (IBAction)btnSetValue:(id)sender
{
[self.delegate sendValue:self didFihishWithValue:self.txtValue.text];
[self dismissViewControllerAnimated:FALSE completion:nil];
}
#end
My question is the following: Considering a, let's say, 30 screens application, which allows sending and receiving messages, adding friends , etc
Is it a good approach to group those 4-5 message view controller into a storyboard, those friends related view controllers into another storyboard and just make the connection like i did in that simple example, programmatically?
I saw that connections can be done in the designer without writing code, but sometimes i think you have to write code to send some arguments which means mixing the two(graphically and programmatically).
I just feel more comfortable, doing it programatically, maybe because this is how i do it in c#.
I am looking forward to you tips regarding organizing and making connections between screens.
PS: Sorry for writing such a long story(board) in here, i promise to make it shorter in my following posts.
Thanks.
Making two storyboards that communicate with each other would go against the intended flow, because storyboards were not intended for grouping parts of an application. Although an app may definitely have multiple storyboards, the intention behind allowing multiple storyboards was letting you support different screen paradigms (i.e. iPhone vs. iPad) or different localizations, not grouping related screens together.
Note, however, that storyboards are relatively new. You can define your views in NIB files, and use them instead. An unfortunate consequence of this choice is that you would need to make all your connections programmatically, but on the other hand you would be able to group your views inside you Xcode project using file groups or folders.
I've worked with NSTableView a couple times before, and I've used this method with no issues, but for some reason in my newest program the tableViewSelectionDidChange: delegate method isn't being called when I switch rows. I've created a very simple program to try to get to the source of this, but for some reason it still isn't working. I know I'm probably overlooking something small but I've been staring at this for hours and comparing it to my other code where it works and I can't see anything.
AppDelegate.h:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate>
//not sure if the NSTableViewDelegate part is needed, as I've used this before without it
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTableView *tableView;
#end
AppDelegate.m:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification{
NSLog(#"Row changed");
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return 2;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return nil;
}
#end
I also had the problem that the tableViewSelectionDidChange: method wasn't called, but only after I've closed and reopened my dialog. It turned out that this "delegate" method does have a notification observer signature for a reason: Apple simply registers your delegate method with NSNotficationCenter. So if you call [[NSNotificationCenter defaultCenter] removeObserver:self]; like I did in my windowDidHide method, you won't get notified about table selection changes any more.
The solution is instead of being lazy and calling [[NSNotificationCenter defaultCenter] removeObserver:self];, you need to unregister only the notifications that you have explicitly observed before.
Additionally insert the following lines and see what happens. Make sure you have set AppDelegate as source and delegate.
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex {
return YES;
}
If that doesn't help, I don't know the cause.
You need to set its data source and delegate to AppDelegate by control-clicking on the tableview control and extending the string to AppDelegate's blue icon, if you haven't.
You need wrap the tableview with a NSViewController such as yourController, set the delegate and dataSource of the tableview to yourController;
self.tableView.delegate = self;
self.tableView.dataSource = self;
Of course, you should implement the delegate methods and the datasource methods in yourController;
Then:
window.contentViewController = yourController;
This works for me.
OK, what am I doing wrong?
1. Created cocoa app and appDelegate named: window2AppDelegate
2. window2AppDelegate.h
#import "PrefWindowController.h"
#interface window2AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
PrefWindowController * ctrl;
}
#property (assign) IBOutlet NSWindow *window;
- (IBAction) buttonClick:(id)sender;
- (IBAction) buttonCloseClick:(id)sender;
#end
3. in xib editor, window connected to window controller - set to appdelegate, buttonclick actions to buttons
4, created
#import <Cocoa/Cocoa.h>
#interface PrefWindowController : NSWindowController {
#private
}
#end
#import "PrefWindowController.h"
#implementation PrefWindowController
- (id)init {
self = [super initWithWindowNibName: #"PrefWindow"];
return self;
}
- (void)dealloc {
// Clean-up code here.
[super dealloc];
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#end
5. created new xib file named PrefWindow window IBOutlet connected to window from its controller (also controller set to PrefWindowController) Option "Visible At Launch" UNCHECKED! i want to see this window on buttonclick.
6. implemented window2AppDelegate
#import "window2AppDelegate.h"
#implementation window2AppDelegate
#synthesize window;
- (id) init {
if ((self = [super init])) {
ctrl = [[PrefWindowController alloc] init];
if ([ctrl window] == nil)
NSLog(#"Seems the window is nil!\n");
}
return self;
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (IBAction) buttonClick:(id)sender {
// [[ctrl window] makeKeyAndOrderFront:self]; this doesn't work too :(
NSLog(#"it is here");
[ctrl showWindow:sender];
}
- (IBAction) buttonCloseClick:(id)sender {
[window close];
}
#end
7. After I build and run app: closebutton closes the app but buttonclick - won't show me PrefWindow!? Why and what am i doing wrong? Don't dell me that to show other window in cocoa objective-c is more difficult than in "stupid" Java or C#?
Finally i've managed the problem! In the nib editor for PrefWindow I had to do: Set File's owner class to: NSWindowController then connect window IBOutlet from File's owner to my (preferneces) window. After 6 days of unsuccessful attempts, google works.
Anyway, thanks for all your responses and time!
I'd suggest you move the creation of the PrefWindowController to applicationDidFinishLaunching:
I am not sure the application delegate's init method is called. Probably only initWithCoder: gets called when unarchiving the object from the NIB.
- (id) init {
if ((self = [super init])) {
ctrl = [[PrefWindowController alloc] init];
if ([ctrl window] == nil)
NSLog(#"Seems the window is nil!\n");
}
return self;
}
init is way too early in the scheme of things to be trying to test IBOutlets. They will still be nil yet. Not until later on in the object creation process will the nib outlets be "hooked up". The standard method where you can know that everything in the nib file has been hooked up is:
- (void)awakeFromNib {
}
At that point, all of your IBOutlets should be valid (provided they're not purposely referencing an object in a separate, yet-unloaded nib).
If PrefWindowController is a class that will only be used after the user chooses Preferences from the app menu, I would have to disagree with the others and say that I would not create the instance of the PrefsWindowController at all during the initial load. (Your main controller should be able to function independently from the prefs window). If you have a method that is meant to load the preferences window, then when that method is called, you should check to see if the PrefsWindowController instance is nil, and if it is, create it, then proceed to show the prefs window.