I'm trying to make a configure sheet appear for a ScreenSaverView subclass. After a long battle with Xcode, I'm finally getting the configure sheet to appear when "Screen Saver Options" is clicked in System Preferences (and my screen saver is selected), and the sheet behaves normally with one exception: the background is black, rendering text invisible (see image).
This occurs regardless of whether the sheet is an NSPanel or NSWindow class, and whether the panel is a Regular Panel, Utility Panel, or HUD Panel. The only thing I can seem to do is change is the alpha value of the panel, which as expected makes everything more transparent (but the text is still not visible). Interestingly, calling setOpaque or setBackgroundColor on the NSPanel or NSWindow don't seem to have any effect.
To figure out why it's showing up as black, we'd really need to see code for how you're creating the window.
I just did a quick test project and it seems to work OK here. IMO, the easiest solution for creating the window to return in the configureSheet method is to use an NSWindowController subclass to load a nib file in which you've configured the window ahead of time.
So in your ScreenSaverView subclass, you'd define an interface something like the following:
#interface MDScreenSaverFinaglerView : ScreenSaverView {
MDScreenSaverOptionsWindowController *optionsWindowController;
NSInteger screenSaverViewMode;
}
#property (nonatomic, retain) MDScreenSaverOptionsWindowController
*optionsWindowController;
#property (nonatomic, assign) NSInteger screenSaverViewMode;
#end
Your implementation would then look like this for the configureSheet method:
- (NSWindow *)configureSheet {
if (optionsWindowController == nil) {
optionsWindowController = [[MDScreenSaverOptionsWindowController alloc]
initWithScreenSaverView:self];
}
return optionsWindowController.window;
}
Basically, you check to see if the optionsWindowController instance exists, creating it if necessary, then return its window.
The interface for the custom NSWindowController subclass would look like the following:
#interface MDScreenSaverOptionsWindowController : NSWindowController {
IBOutlet NSMatrix *optionsMatrix;
MDScreenSaverFinaglerView *screenSaverView; // non-retained/weak reference
}
- (id)initWithScreenSaverView:(MDScreenSaverFinaglerView *)aView;
#property (nonatomic, assign) MDScreenSaverFinaglerView *screenSaverView;
- (IBAction)ok:(id)sender;
#end
There's a screenSaverView property which will allow communication back with the ScreenSaverView subclass once the user has clicked the OK button.
The nib file for the MDScreenSaverOptionsWindowController class (named "MDScreenSaverOptionsWindowController.xib") is set up like shown below:
The implementation of the MDScreenSaverOptionsWindowController looks like the following:
#implementation MDScreenSaverOptionsWindowController
#synthesize screenSaverView;
- (id)initWithScreenSaverView:(MDScreenSaverFinaglerView *)aView {
NSParameterAssert(aView != nil);
if ((self = [super initWithWindowNibName:NSStringFromClass([self class])])) {
self.screenSaverView = aView;
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
[optionsMatrix selectCellWithTag:screenSaverView.screenSaverViewMode];
}
- (IBAction)ok:(id)sender {
NSInteger viewMode = [optionsMatrix selectedTag];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber
numberWithInteger:viewMode] forKey:MDScreenSaverViewModeKey];
screenSaverView.screenSaverViewMode = viewMode;
[NSApp endSheet:self.window];
}
#end
The end result:
Sample project: ScreenSaverFinagler.zip
Related
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 working on a front end for a project in objective-c and I am having some trouble getting methods of my class Window which is a subclass of NSViewController to fully execute when called from a different class.
I have a method of the class Window that is called setColor which changes the color of my NSTableView variable which is linked to a bordered scroll view in my interface. I am able to successfully change the color by calling the setColor method like this from the init method in Window: [self setColor :self];
However when I do this [window1 setColor: window1] with window1 being an object of the class Window that I have declared in class Door, nothing seems to happen since the color of the boarded scroll view remains the same.
My Window.h file looks like this:
#interface Window : NSViewController {
#public
IBOutlet NSTableView *dataTableView;
}
#property (retain) IBOutlet NSTableView *tableView;
- (IBAction)SetColor:(id)sender;
#end
My Window.m looks like this:
#synthesize tableView;
- (void) awakeFromNib {
// [self SetColor :self];
}
- (IBAction)SetColor:(id)sender;
{
NSLog(#"changing the color");
[self->tableView setBackgroundColor: NSColor.blueColor];
}
Door.h looks like this
#interface Door : NSViewController {
Window* window1;
}
-(IBAction)buttonPress:(id)sender;
#property (retain) Window* window1;
#end
Door.m looks like this:
-(void) dealloc{
[window1 release];
}
-(id)init{
self = [super init];
if(self){
window1 = [Window alloc];
}
-(IBAction)buttonPress :(id)Sender;
{
[window1 setColor: window1];
}
I am using Xcode 3.2 so I cannot use ARC.
window1 = [Window alloc] will not load a Nib or storyboard and connect the outlet IBOutlet NSTableView *dataTableView to the table view inside it.
If "Window" is a view controller, you need to initialize it and the outlets in it a more standard way. View controllers need the proper initialization or the outlets are nil, and in Objective-C, if you send a method to nil, it just does nothing.
I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.
I'm trying to create a simple Quiz app (I'm a beginner), when I launch the app I want a UILabel to show the first question (of an array of questions). I'm having some trouble with setting the initial value.
I've done a couple of attempts, whiteout success. I my QuizAppDelegate.h file I declare my UILabel like this:
IBOutlet UILabel * questionField;
In my main .m file I have tried the following:
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
questionField = [[UILabel alloc] init];
[questionField setText:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
Another thing I have tried is the following in QuizAppDelegate:
#property (nonatomic, retain) IBOutlet UILabel *questionField;
- (void)changeTitle:(NSString *)toName;
And in the .m file:
#synthesize questionField;
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
[self changeTitle:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
-(void)changeTitle:(NSString *)toName {
[questionField setText:toName];
}
Any tips on how to solve this would be great!
// Anders
Hopefully you're not actually putting code into main.m. On iOS, you rarely modify that file.
Since you're doing everything in the AppDelegate, let's keep it there (as opposed to creating a new UIViewController). Let's start with the basics.
Adding the Label as an instance variable
You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;
Then, declare the corresponding property, and make sure to synthesize it in the .m file.
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#synthesize questionField // in the .m file
Adding the UILabel in Interface Builder
Open up MainWindow.xib. Drag a UILabel from the Library to the Window that represents your app's window. Then Control-Drag from the AppDelegate object (the third icon on the left in Xcode 4; it'll be labelled in the Document window in IB 3). You'll see a little black window come up—select the option called questionField to make the connection.
See this link for screenshots and how to make connections in IB. The same applies in Xcode 4.
Changing the text
You don't need a separate method to change the text—just modify the label's text property.
Pick a method that'll be called when the app launches (applicationDidFinishLaunching:WithOptions: is a good place to do it in), and put the following code:
questionField.text = #"Hello";
And that's it!
Code
QuizAppDelegate.h
#import <UIKit/UIKit.h>
#interface QuizAppDelegate : NSObject <UIApplicationDelegate> {
IBOutlet UILabel *questionField;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#end
QuizAppDelegate.m
#import "QuizAppDelegate.h"
#implementation QuizAppDelegate
#synthesize window=_window;
#synthesize questionField;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
[self.window addSubview:self.questionField];
[self.window makeKeyAndVisible];
self.questionField.text = #"Hello";
return YES;
}
- (void)dealloc
{
[_window release];
[questionField release];
[super dealloc];
}
#end
If you're creating the label programmatically, then you have to add the label to the view:
[self.view addSubview:questionField];
This assumes that you have a ViewController. If not, and you're doing this directly in the AppDelegate (a very bad idea, by the way), then do
[self.window addSubview:questionField];
If you're creating it in the IB, make sure you set up the connections.
You should not both add the UILabel in the IB and instantiate it programmatically. Only call alloc if you are creating it programmatically. Otherwise, if using the IB, skip that part. You created it already with the xib.
I suspect that you have either not created your Interface Builder layout properly - either you have missed the control out all together or more likely you have not connected that control to the questionField outlet in yout header file.
You need to drag a UILabel view into the main view and then connect it to the correct line in your header file.
You shouldn't be using your main.m like that at all. In fact, you should almost certainly never do anything with it. Try creating a UIViewController subclass and practicing your quiz with that. (Add the UILabel to the IB file and then connect the outlet.) Perhaps use the View-Based Application template while you are practicing.
This is a good answer:
"You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;"
I was trying to change the value of mylabel.text and the screen didn't update the label with this.value. I included the {IBOutlet UILabel * mylabel} and it works like a charm!
So this answer is valid to change the text of a label programmatically!
Thanks
Let me start off by saying that this is my first real Cocoa app. It's a simple app that pretty much displays my website in a borderless window. The way I'm currently creating a borderless window is using the following:
- (void) awakeFromNib {
[window setStyleMask:NSBorderlessWindowMask];
[window setAcceptsMouseMovedEvents:YES];
[window setMovableByWindowBackground:YES];
[window setLevel:NSNormalWindowLevel];
}
The problem with this is that as a result, the WebView within the window does not pass mouse over events to elements on the loaded page, nor does it provide the ability to type in text fields. I know that I'm supposed to create a custom window instead and move the contentView into it but I'm too new to Objective-C to figure out how.
I've also tried declaring all of these with no luck:
#implementation specikAppDelegate
#synthesize window;
#synthesize webView;
- (BOOL) canBecomeKeyWindow { return YES; }
- (BOOL) canBecomeMainWindow { return YES; }
- (BOOL) acceptsFirstResponder { return YES; }
- (BOOL) becomeFirstResponder { return YES; }
- (BOOL) resignFirstResponder { return YES; }
...
#end
Additionally, I'd like to be able to move the window by clicking and dragging it anywhere but that's a side thought. I've searched extensively online, and cannot find a solution to this.
Contents of my .h file (just in case):
#interface specikAppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSWindow *window;
IBOutlet WebView *webView;
}
#property (assign) IBOutlet NSWindow *window;
#property (nonatomic, retain) IBOutlet WebView *webView;
- (IBAction)openAboutPanel:(id)sender;
#end
Any help would be appreciated, and like I said, I'm super new to the world of Objective-C and Cocoa, but I do come from a PHP development background.
As explained in this answer, windows without title or resize bar (including borderless windows) cannot become key windows.
You were right about overriding -canBecomeKeyWindow, but you’ve missed the correct place. You shouldn’t do it in your application delegate. You need to create an NSWindow subclass and then override that method.
This sample code of apple should give you the information you need, its really easy to change the way it works and change it into your own drawn NSWindow ( without a border :D )
http://developer.apple.com/library/mac/#samplecode/RoundTransparentWindow/Introduction/Intro.html