Setting the NSImage of a NSImageView from a NSURL - objective-c

Okay, so I'm a newbie trying to write a practice app where you click a button, an NSOpenPanel appears, you select an image file, and the image gets displayed in an NSImageView.
I've got the open panel working okay, and it returns an NSArray of NSURLs. I avoided using filename paths as the Apple docs said it was depreciated. I then try to make an NSImage object using initWithContentsOfURL, and then try to setImage of the NSImageView to the new image.
Here's what I think are the relevant parts of the implementation...
- (IBAction)openImage:(NSButton *)sender {
NSLog(#"%# was clicked", sender);
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
if ([panel runModal] == NSModalResponseOK)
{
NSArray* selectedFile = [panel URLs];
NSLog(#"%# was selected", selectedFile[0]);
NSImage *theImage = [[NSImage alloc] initWithContentsOfURL:selectedFile[0]];
[imageViewer setImage:theImage];
}
}
And from the header:
#property (weak) IBOutlet NSImageView *imageViewer;
- (IBAction)openImage:(NSButton *)sender;
#end
When I try to set the image, Xcode says there's imageViewer is undeclared, and wants to correct it to _imageViewer. I tried following its advice and ran it, but then the app just crashes once I select a file from the open panel so something is obviously still not right.
The NSLog lines shows the button was clicked, and shows the correct URL of the file selected, but I'm having issues setting the NSImageView. It's probably something really simple but I can't seem to figure it out.

You need to declare the variable imageViewer in your headers (.h) interface , for example:
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSImageView *__weak imageViewer;
}
#property (weak) IBOutlet NSImageView *imageViewer;
- (IBAction)openImage:(NSButton *)sender;
#end
Then in your class (.m) just synthesize the variable:
#implementation AppDelegate
#synthesize imageViewer;
...
Since you hadn't declared the variable in the interface you were receiving the error.

Try this code.
- (IBAction)openImage:(NSButton *)sender {
NSLog(#"%# was clicked", sender);
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
if ([panel runModal] == NSModalResponseOK)
{
NSArray* selectedFile = [panel URLs];
NSLog(#"%# was selected", selectedFile[0]);
NSURL *url = (NSURL *)selectedFile[0];
NSString *filePath = [url absoluteString];
NSImage *theImage = [[NSImage alloc] initWithContentsOfFile:filePath];
[imageViewer setImage:theImage];
}
}

Okay, so I feel stupid now. I'm still learning stuff so I wasn't sure why it was freezing up until I noticed Xcode kept switching to the "Debug" panel, which clued me into what might be happening.
I figured out I accidentally clicked a line number, which apparently sets breakpoints for debugging. I also might have somehow clicked "Toggle global breakpoint state" in the bottom Debug area.
Because I selected the line where the setImage was, it looked like the app crashed/froze whenever it hit that line, but it actually just paused the app to allow me to look at what's going on with the app and debug it. I deselected the lines and turned off the global breakpoint state button and everything runs fine now.
A newbie coder mistake from somebody not used to working in an IDE. :P

Related

How to create a custom NSView for NSSavePanel in Cocoa MacOS objective C?

I need to add a save extension selector with a text label next to it to my NSSavePanel. In the screenshot attached I try to demonstrate that I succeeded in adding an NSComboBox to my panel with the function setAccessoryView. However I have no idea how to create a custom NSView, which includes both an NSComboBox and an NSTextView or equivalent. I found no tutorials on the internet (or if I found one it was extremely outdated) showing how to create custom NSViews in objective-C in Cocoa on MacOS.
How can I create a custom NSView containing a combobox and a text label? Or how can I add two "stock" NSViews to the same NSSavePanel? Please be as detailed in your answer as possible, as I have very limited objective-c experience.
You asked how to create an NSView in Objective-C with an NSTextField and an NSComboBox as subviews.
Basically, you could define them in Interface Builder and programmatically set the resulting view in Objective-C as the accessoryView of the NSSavePanel. Alternatively, the custom NSView could be created entirely in Objective-C, which is probably the easier option here.
After instantiating an NSView, you can use addSubview: to add an NSTextField and an NSComboBox accordingly. Then you can use NSLayoutConstraints to set up Auto Layout, which takes care of sizing the accessoryView and arranging the subviews properly based on the width of the dialog.
If you create the views programmatically and use Auto Layout, you must explicitly set translatesAutoresizingMaskIntoConstraints to NO.
Should you want to set the allowedContentTypes, a textual mapping of the displayed extension to UTType via a NSDictionary might be useful.
If you set the delegate of the NSComboBox to self, then you will be informed about changes of the user selection in the NSComboBox via comboBoxSelectionDidChange:.
If the things discussed are implemented appropriately in code, it might look something like this for a self-contained example:
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "ViewController.h"
#interface ViewController () <NSComboBoxDelegate>
#property (nonatomic, strong) NSSavePanel *savePanel;
#property (nonatomic, strong) NSDictionary<NSString *, UTType*> *typeMapping;
#end
#implementation ViewController
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
_typeMapping = #{
#"jpeg": UTTypeJPEG,
#"png": UTTypePNG,
#"tiff": UTTypeTIFF
};
}
return self;
}
- (NSView *)accessoryView {
NSTextField *label = [NSTextField labelWithString:#"Filetypes:"];
label.textColor = NSColor.lightGrayColor;
label.font = [NSFont systemFontOfSize:NSFont.smallSystemFontSize];
label.alignment = NSTextAlignmentRight;
NSComboBox *comboBox = [NSComboBox new];
comboBox.editable = NO;
for (NSString *extension in self.typeMapping.allKeys) {
[comboBox addItemWithObjectValue:extension];
}
[comboBox setDelegate:self];
NSView *view = [NSView new];
[view addSubview:label];
[view addSubview:comboBox];
comboBox.translatesAutoresizingMaskIntoConstraints = NO;
label.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:#[
[label.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-12],
[label.widthAnchor constraintEqualToConstant:64.0],
[label.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:0.0],
[comboBox.topAnchor constraintEqualToAnchor:view.topAnchor constant:8.0],
[comboBox.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:8.0],
[comboBox.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-8.0],
[comboBox.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-20.0],
]];
return view;
}
- (void)comboBoxSelectionDidChange:(NSNotification *)notification {
NSComboBox *comboBox = notification.object;
NSString *selectedItem = comboBox.objectValueOfSelectedItem;
NSLog(#"### set allowedContentTypes to %# (%#)", selectedItem, self.typeMapping[selectedItem]);
[self.savePanel setAllowedContentTypes:#[ self.typeMapping[selectedItem] ]];
}
- (IBAction)onSave:(id)sender {
NSWindow *window = NSApplication.sharedApplication.windows.firstObject;
self.savePanel = [NSSavePanel new];
self.savePanel.accessoryView = [self accessoryView];
[self.savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) {
if (result != NSModalResponseOK) {
return;
}
NSURL *fileURL = self.savePanel.URL;
NSLog(#"### selectedFile: %#", fileURL);
}];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
}
#end
Finally, a screenshot of the above demo code in action looks like this:
Press Cmd-N to add a new file to your project. Choose a View file to add a xib file that has a custom view.
Open the xib file and add the controls to the custom view. Press the Add button in the project window toolbar to access the user interface elements.
Use the NSNib class to load the xib file and get the custom view.

NSPopover deallocation when losing focus (OSX, Objective-C)

I am on OSX (not iOS), Xcode 8.2, ARC enabled, Objective-C.
I have a view that opens a popover on a button click. Both are connected with a delegate and a protocol that allows access to the following methods (among others)
- (id)valueForKey:(NSString*)key;
- (void)setValue:(id)value forKey:(NSString *)key;
(I'm using this protocol very often and need to keep it as clean and unspecific as possible)
On the popover's view there's another button that opens an NSOpenPanel. Once the panel opens, the popover dissapears and get's deallocated - which is the preferred behaviour... usually. Unfortunately i need the openPanel to save to the parent view via the protocol methods. But if the popover gets deallocated those methods are not available anymore.
So i tried to create a strong reference to the value i want to save to before the openPanel and release it afterwards to keep the popover viewController in memory. But once i try to release that... it crashes.
#interface SHWildcardItemSettingImageViewController ()
#property (strong) NSString* customImagePathBookmark;
#end
#implementation SHWildcardItemSettingImageViewController
// Create strong reference to delegate (to not dealloc popoverViewController)
[self setCustomImagePathBookmark:[_delegate valueForKey:#"customImagePathBookmark"]];
// Setup open panel
NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanChooseFiles:NO];
[openPanel setCanCreateDirectories:YES];
[openPanel setPrompt:#"Choose folder"];
// Display the dialog box. If the OK pressed, process the folder.
if ( [openPanel runModal] == NSModalResponseOK ) {
// Gets list of all files selected
NSArray *files = [openPanel URLs];
// Create and save Bookmark for later use (_bookmarks is a custom NSObject)
[_delegate setValue:[_bookmarks bookmarkFromURL:[files objectAtIndex:0]] forKey:#"customImagePathBookmark"];
}
[self setCustomImagePathBookmark:nil]; // <---- Crashes here
#end
What would be the best way to keep the popover allocated while the NSOpenPanel is in focus WITHOUT using different NSPopoverBehaviours.
EDIT:
Sometimes you don't see it... thanks to #Ron Gahlot
- Solved with a simple bool check:
- (BOOL)popoverShouldClose:(NSPopover *)popover
{
// Check if imageSettings can close
if ([popover.contentViewController isMemberOfClass:[SHWildcardItemSettingImageViewController class]])
return ![[popover.contentViewController valueForKey:#"lockPopover"] boolValue];
return YES;
}
you use a bool variable. when openDialog is open return NO in
- (BOOL)popoverShouldClose:(NSPopover *)popover;

Can't get UITouch event from UIButton

Problem:
I can't get my UIButton to send my Class an Event call
Here is what I tried first:
I made a class which extends UIViewController, called Viewer
#import "Viewer.h"
#import "MainScreen.h"
#implementation Viewer
- (void)viewDidLoad
{
MainScreen* main = [[MainScreen alloc]init: self];
[main show];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
then:
I created a class called MainScreen, 2 IBOutlets - singlePlayerButton and view
Viewer is an instance of UIViewController
buttonPressed has the return type of IBAction
MainScreen:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
{
IBOutlet UIButton* singlePlayerButton;
IBOutlet UIView* view;
Viewer* viewer;
}
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
}
- (IBAction)buttonPressed:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Then I created an NIB called MainScreenLayout.xib
Here is what it looks like:
As you can see, I have four buttons but the only one that is connected to anything is the Single-Player button I also have the UIView connected to view.
Now, I linked the action to the Single-Player button like this:
buttonPressed is my method that I want to have called when the button is pressed, so I ctrl-clicked on First Responder and then clicked on the circle next to buttonPressed and then dragged it on top of the Single-Player button. Then the next dialog window popped up and I clicked on Touch up Inside. Here is what the Received Actions window looks like:
Now, I thought at this point it should work. I ran it on my iPhone Simulator and pressed the button.
Just so there is no question of whither or not I'm competent, here is a picture of me actually clicking the button:
I got no output whatsoever. Then I went to my code and looked at the method. This is what it looks like:
as you can see, the oval is not filled, meaning that the method is not paired. So naturally I thought it was being overridden by another method. So here is what the method looks like now:
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
Then I went back to my NIB window, unpaired my Single-Player button and then repaired it to aVerySpecificMethodNameForMyButtonListener. Now this is what it looks like:
Then I ran it again and still nothing when I pressed the button. Then I went back to the code:
(insert frustration here)
At this point I restarted Xcode and the Simulator and looked over everything again and it still didn't work.
This is what I tried next which seems about a billion times more simple:
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
I replaced with my show method with the one above. The only thing different is the last line. I disconnected my method from the Single-Player button in the NIB. Then I ran it and I got excited for a second because I got some output. Here is what I got:
(lldb)
Very helpful output C debugger, as always. I spent the next few hours on SO trying to find out what I'm doing wrong. I've tried countless examples and none of them worked.
Does anybody see what I'm doing wrong here? Is it just Xcode being flakey?
Thanks for reading!! All relevant answers are welcomed!
UPDATE:
I also tried changing the declaration of UIButton to a property in the Header file:
#import <Foundation/Foundation.h>
#import "Viewer.h"
#interface MainScreen : NSObject
{
IBOutlet UIView* view;
Viewer* viewer;
}
#property (weak, nonatomic) IBOutlet UIButton* singlePlayerButton;
- (id)init:(Viewer*) theViewer;
- (void)show;
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender;
#end
Thus here is what I changed in MainScreen.m to comply:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
#synthesize singlePlayerButton;
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Now there are no variable declarations inside of the MainScreen.m file, I moved them all to the Header. The circles next to singlePlayerButton and view are still filled in but the bubble next to the method is not. I ran the code again and got the same output.
So I'd have put this in a comment because its really just a suggestion however I dont have the rep yet so sorry to get your hopes up, but I believe the touch event's scope is only inside the UIView in which it happens. When the touch up event gets triggered it doesn't get passed to the UIView's handler (MainScreen class) and so the event's listener method does not get called... but that you are able to link it in the interface builder to the button is baffling to me, so i could be way off.
Anyways, where I think you should go next is to make your MainScreen a UIView object and see what that does for event handling of the buttons in the view.

MPMoviePlayerViewController Black Screen issue!

I have this code trying to run a video on the iPhone 4 Simulator.
The problem is that looks like it loads the player but half a second later loads a back screen on top of the whole application disabling touch and everything, and it looks like it does not play the video also, because I can't hear anything.
Any ideas?!
MPMoviePlayerViewController *mp =
[[MPMoviePlayerViewController alloc] initWithContentURL:videoUrl];
if (mp) {
mp.moviePlayer.scalingMode = MPMovieScalingModeFill;
mp.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[mp.moviePlayer play];
[self presentMoviePlayerViewControllerAnimated:mp];
[mp release];
}
I believe the problem is caused by releasing the MPMoviePlayerViewController. Simply retain the controller until you are done with it.
Prior to the "[mp release];" add this line to save the value away.
self.moviePlayerViewController = mp;
Then update your dealloc method to do the release:
- (void)dealloc {
[_moviePlayerViewController release], _moviePlayerViewController = nil;
[super dealloc];
}
Add the synthesize to the top of your .m file:
#synthesize moviePlayerViewController = _moviePlayerViewController;
Add the defination to the #interface of your .h file:
MPMovieViewController *_moviePlayerViewController;
Add the property to your .h file:
#property (readwrite, retain) MPMovieViewController *moviePlayerViewController;
You may need some headers in your header:
#import <MediaPlayer/MediaPlayer.h>
#import <MediaPlayer/MPMoviePlayerViewController.h>
You may also need to balance your "presentMoviePlayer" call with the dismiss somewhere:
[self dismissMoviePlayerViewControllerAnimated];
Phew, code everywhere. Anyway, if you are finished with the resource early, you may be able to release it sooner by using NotificationManager to watch for MPMoviePlayerPlaybackDidFinishNotification. There are many examples of that, so I won't repeat it.
Hope this helps.
This is the code I'm using:
MPMoviePlayerViewController *movieViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:contentUrl]];
movieViewController.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[self presentMoviePlayerViewControllerAnimated:movieViewController];
[movieViewController release];
It seems to work fine for me. Two notes:
Some simulators (like the current iOS 5.0) crash when playing a movie, but it works on a real device
if you leave out the movieSourceType part, a black screen is shown for about a second before the movie starts

Setting a ViewController's properties after instantiation

I'm creating an instance of a viewController, and then trying to set the text on of it's properties, a UILabel.
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", boyViewController.sign.text);
[newText release];
I tried this, but it didn't work...
So I tried the following:
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
UILabel *newUILabel = [[UILabel alloc] init];
newUILabel.text = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign = newUILabel;
NSLog(#" the boyviewcontroller.sign.text is now set to: %#", newUILabel.text);
[newUILabel release];
But no avail..
I'm not sure why I can't set the text property of the UILabel "sign" in boyViewController..
The problem here is that the initializer does not actually load the nib file into memory. Instead, loading the nib is delayed until your application requests the view controller's view property. As such, your controller's sign property is null when you access it.
Manually requesting the controller's view property would make your example work...
BoyController *boyViewController = [[BoyController alloc] initWithNibName:#"BoyView" bundle:nil];
[boyViewController view]; // !!!: Calling [... view] here forces the nib to load.
NSString *newText = [astrology getSignWithMonth:month withDay:day];
boyViewController.sign.text = newText;
// and so on...
However, I'd guess that what you're really trying to do is create and configure your view controller before setting it free to do it's own thing. (Perhaps to display it modally, say.) Calling [... view] manually is not going to be a long-term solution.
Better is to set a separate property on your view controller for the label text and then implement viewDidLoad to assign it to the label:
#interface BoyViewController : UIViewController {
IBOutlet UILabel *label;
NSString *labelText;
}
#property(nonatomic, copy)NSString *labelText;
#end
#implementation
#synthesize labelText;
- (void)viewDidLoad
{
[label setText:[self labelText]];
}
// and so on...
#end
This has the added benefit of your label text being reset in case the view is purged during a low memory event.
Did you bind your outlets at Interface Builder?
It seems that you need to bind sign outlet of the first example into Interface Builder in order to actually set that text to whatever you want.
Once you bind your outlet to the actual UI component at Interface Builder, then you should be able to do something like:
NSString *newText = [astrology getSignWithMonth:month withDay:day];
[[boyViewController sign] setText:newText];
This is what you need to know about binding.
Your second example does not make sense at all to me.