NSArrayController bound to NSCollectionview - objective-c

I'm kinda new into bindings, somehow prevented it. But I want to use them now.
Talking about OSX and this is programmed in code not in IB.
So, I have data coming from CoreData into my ArrayController. A NSCollectionView is bound to this arraycontroller and if there is data, this binding works the data is displayed.
But, each item has some buttons, sliders, textfields. On a click, a code will change the tag or value of those things. I thought it is enough when I send the change to coredata and save it. Shouldnt the arraycontroller get this and update my items in the collectionview?
Because the tags(first thing I tried) dont get updated if its updated in coredata.
Do those fields have to be somehow bound?
the tag is set in a subclass of NSCollectionViewItem this way:
[[(BEItem *)[self view] valueSlider] setTag:[[representedObject itemTag] intValue]];
Is there anything I have to tell the CollectionView to update itself and take the new data from the controller?
Thanks
Benjamin
EDIT
I have changed my collectionview. I read it isnt really possible to bind a representable object and in the answer below, it is bound to some property, but this property isnt updated either. Then I read about newItemForRepresentedObject that you should use this function. Now, I created everything like shown below, but the program is always crashing after 10 seconds or something and nothing is displayed. It is continuously calling setChannelID, but never setting the ID to the property. Because of that it is always called I think this is the problem. (The if gets never to only return)
What is the problem here? Im really confused by the collectionview by now.
And this is just code, nothing in IB.
Setting up the View in appdelegate:
NSCollectionViewItem *testitem = [[NSCollectionViewItem alloc] init];
[testitem setView:[ChannelView new]];
self.collectionView = [[ChannelCollectionView alloc] initWithFrame:NSMakeRect(10, 0, mixerWidth, self.splitview.frame.size.height)]; // initWithFrame:[[[self window] contentView] frame]
[self.collectionView setItemPrototype:testitem];
[self.collectionView setMaxNumberOfRows:1];
[self.collectionView setAutoresizingMask:(NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin | NSViewMinYMargin | NSViewHeightSizable| NSViewMaxYMargin)];
[self.collectionView setAutoresizesSubviews:YES];
[self.collectionView bind:NSContentBinding toObject:self.channelController withKeyPath:#"arrangedObjects" options:nil];
ChannelView:
#import <Cocoa/Cocoa.h>
#interface ChannelView : NSView
#property (readwrite, nonatomic, copy) NSString *channelName;
#property (readwrite, nonatomic, copy) NSNumber *channelID;
#property (readwrite) NSTextField *channelNameField;
#property (readwrite) NSTextField *deviceChannelField;
#end
#implementation ChannelView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:NSMakeRect(0, 0, 300, 500)];
if (self) {
// Initialization code here.
ColorView *test = [[ColorView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
self.channelNameField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)];
self.deviceChannelField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 50, 100, 20)];
[self addSubview:test];
[self addSubview:self.channelNameField];
[self addSubview:self.deviceChannelField];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
//add die teile
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
// setters.
-(void)setChannelID:(NSNumber *)chanID
{
//NSLog(#"hallo");
if (self.channelID == chanID) {
return;
NSLog(#"da");
}
else {
NSLog(#"hello"); //just this in debug output
self.channelID = [chanID copy];
NSLog(#"no output");
// self.channelID = chanID;
NSLog(#"chanid %d current: %d", chanID.intValue, self.channelID.intValue); //never shown in debug
[self.deviceChannelField setStringValue:[NSString stringWithFormat:#"%d",self.channelID.intValue]];
}
}
#end
And this piece in my subclasses NSCollectionView
- (NSCollectionViewItem *)newItemForRepresentedObject:(ChannelsToMixes*)object
{
NSCollectionViewItem *item = [super newItemForRepresentedObject:object];
// ChannelView *view = (ChannelView *)[item view];
NSLog(#"cahnnelid: %d",object.channelID.intValue);
// [view bind:#"title" toObject:object withKeyPath:#"title" options:nil];
[item.view bind:#"channelID" toObject:object withKeyPath:#"channelID" options:nil];
//NSLog(#"test");
//NSLog(#"%#",object);
return item;
}
If anyone knows why the setter isnt setting the property give me a tip :)
It should be able to do this and is not released or anything, at least that I know of (using ARC)

Yes, you have to bind the value of your slider to your CollectionViewItem.
You can either do this in code with this method:
-bind:toObject:withKeyPath:options:
Which would look in your example like this:
[[(BEItem *)[self view] valueSlider] bind:#"tag" toObject:self withKeyPath:#"itemTag" options:nil];
Or, if you use IB, in the InterfaceBuilder by setting the value to bind to your Colletion View Item representedObject.itemTag

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.

Passing data to a UIView - failure

I have a strange problem that I've never encountered before,
I have data in my viewController that I want to display in a UIView.
It's an iPad App which involve a SplitView Controller, when I click on an element within the table view (masterView) it execute a function in my detailViewController (via a protocol).
A function is executed which launch a UIView and send data to it:
myController:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}
DocumentView.h
#import <UIKit/UIKit.h>
#import "Document.h"
#import "DocumentInfo.h"
#class Document;
#class DocumentInfo;
#interface DocumentView : UIView
#property(strong,nonatomic) Document *doc;
#property(strong,nonatomic) DocumentInfo *doc_info;
#end
DocumentView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILabel *titreDoc=[[UILabel alloc] initWithFrame:CGRectMake(20, 32, 339, 21)];
titreDoc.textColor = [self makeColorRGB_RED:66 GREEN:101 BLUE:149];
titreDoc.font = [UIFont fontWithName:#"System" size:(24.0)];
[self addSubview:titreDoc];
NSLog(#"%# - %#",doc,doc_info);
titreDoc.text=#"Nouveau Document";
}
return self;
}
My view is well displayed (I mean the label appear) but impossible to get the data which would have been passed to it... (the NSLog print (null) (null) )
Anybody know the reason why?
The problem seems pretty straight forward. You initialize your view (which means that you run - (id)initWithFrame:(CGRect)frame) and after that you're setting the data, so it's normal that you see null values into your init method because the ivars have not being set yet. What you could do is to modify your init method in order to construct your view taking into account these ivars. Something like this perhaps:
- (id)initWithFrame:(CGRect)frame doc:(Document *)doc docInfo:(DocumentInfo *)docInfo;
ps. If you choose to make your custom init method do not forget to call the designated initializer (-initWithFrame:) before any customization.
The reason the NSLog prints null is because the doc and doc_info are nil when the initWithFrame method is called. The doc and doc_info properties are set after the initWithFrame method is called in selectionChanged: method. Add the NSLog function after line 3 in selectionChanged method like this:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
NSLog(#"%# - %#",doc,doc_info);
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}

How do I get NSTextFinder to show up

I have a mac cocoa app with a webview that contains some text. I would like to search through that text using the default find bar provided by NSTextFinder. As easy as this may seem reading through the NSTextFinder class reference, I cannot get the find bar to show up. What am I missing?
As a sidenote:
- Yes, I tried setting findBarContainer to a different view, same thing. I reverted back to the scroll view to eliminate complexity in debugging
- performTextFinderAction is called to perform the find operation
**App Delegate:**
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.textFinderController = [[NSTextFinder alloc] init];
self.webView = [[STEWebView alloc] initWithFrame:CGRectMake(0, 0, self.window.frame.size.width, 200)];
[[self.window contentView] addSubview:self.webView];
[self.textFinderController setClient:self.webView];
[self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
[[self.webView mainFrame] loadHTMLString:#"sample string" baseURL:NULL];
}
- (IBAction)performTextFinderAction:(id)sender {
[self.textFinderController performAction:[sender tag]];
}
**STEWebView**
#interface STEWebView : WebView <NSTextFinderClient>
#end
#implementation STEWebView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
- (NSUInteger) stringLength {
return [[self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"] length];
}
- (NSString *)string {
return [self stringByEvaluatingJavaScriptFromString:#"document.documentElement.textContent"];
}
In my tests, WebView.enclosingScrollView was null.
// [self.textFinderController setFindBarContainer:self.webView.enclosingScrollView];
NSLog(#"%#", self.webView.enclosingScrollView);
Using the following category on NSView, it is possible to find the nested subview that extends NSScrollView, and set that as the container, allowing the NSTextFinder to display beautifully within a WebView
#interface NSView (ScrollView)
- (NSScrollView *) scrollView;
#end
#implementation NSView (ScrollView)
- (NSScrollView *) scrollView {
if ([self isKindOfClass:[NSScrollView class]]) {
return (NSScrollView *)self;
}
if ([self.subviews count] == 0) {
return nil;
}
for (NSView *subview in self.subviews) {
NSView *scrollView = [subview scrollView];
if (scrollView != nil) {
return (NSScrollView *)scrollView;
}
}
return nil;
}
#end
And in your applicationDidFinishLaunching:aNotification:
[self.textFinderController setFindBarContainer:[self scrollView]];
To get the Find Bar to appear (as opposed to the default Find Panel), you simply have to use the setUsesFindBar: method.
In your case, you'll want to do (in your applicationDidFinishLaunching:aNotification method):
[textFinderController setUsesFindBar:YES];
//Optionally, incremental searching is a nice feature
[textFinderController setIncrementalSearchingEnabled:YES];
Finally got this to show up.
First set your NSTextFinder instances' client to a class implementing the <NSTextFinderClient> protocol:
self.textFinder.client = self.textFinderController;
Next, make sure your NSTextFinder has a findBarContainer set to the webView category described by Michael Robinson, or get the scrollview within the webView yourself:
self.textFinder.findBarContainer = [self.webView scrollView];
Set the find bar position above the content (or wherever you wish):
[self.webView scrollView].findBarPosition = NSScrollViewFindBarPositionAboveContent;
Finally, tell it to show up:
[self.textFinder performAction:NSTextFinderActionShowFindInterface];
It should show up in your webView:
Also, not sure if it makes a difference, but I have the NSTextFinder in the XIB, with a referencing outlet:
#property (strong) IBOutlet NSTextFinder *textFinder;
You may also be able to get it by simply initing it like normal: self.textFinder = [[NSTextFinder alloc] init];

Property is null after adding subview

I'm launching a UIPopoverViewController that is supposed to draw two buttons (add and delete buttons). The ContentViewController for the UIPopover has a property called outputJackView that is set just before the UIPopoverViewController is launched. This property is necessary for the buttons to draw properly. The problem is right after the first button is added as a subview, the outputJackView is set to null somehow.
Here is the ContentViewController for UIPopoverViewController:
CableConnectionMenuController.h
#import <UIKit/UIKit.h>
#class JackView;
#interface CableConnectionMenuController : UIViewController
{
JackView *outputJackView;
}
#property (nonatomic, weak) id <CableConnectionDelegate> delegate;
#property (nonatomic, strong) JackView *outputJackView;
- (void)setButtonTextWithOutputJack:(JackView *)outputJack withInputArray:(NSMutableArray *)inputArray;
- (void)createAddConnectionButton;
- (void)createDeleteConnectionButton;
#end
CableConnectionMenuController.m
#import "CableConnectionMenuController.h"
#import "JackView.h"
#import "CableDisconnectButton.h"
#implementation CableConnectionMenuController
#synthesize delegate;
#synthesize outputJackView;
...
- (void)viewDidLoad
{
[super viewDidLoad];
//alloc output jack view
self.outputJackView = [[JackView alloc] init];
//set size of popover view in cables view
self.contentSizeForViewInPopover = CGSizeMake(200, 200);
//change view background color
self.view.backgroundColor = [UIColor whiteColor];
}
//this method is called from the class that launches the UIPopoverViewController
- (void)setButtonTextWithOutputJack:(JackView *)outputJack withInputArray:(NSMutableArray *)inputArray
{
//set output jack which will be the same for all inputs
self.outputJackView = outputJack;
//draw add connection button
[self createAddConnectionButton];
//draw delete connection button - not working
//[self createDeleteConnectionButton];
}
- (void)createAddConnectionButton
{
CableDisconnectButton *addConnectionButton = [CableDisconnectButton buttonWithType:UIButtonTypeCustom];
addConnectionButton.frame = CGRectMake(0, 0, 190, 40);
[addConnectionButton setBackgroundImage:[UIImage imageNamed:#"images/cable_connect_button.png"] forState:UIControlStateNormal];
[addConnectionButton setBackgroundImage:[UIImage imageNamed:#"images/cable_connect_button_over.png"] forState:UIControlStateHighlighted];
//add output jack
addConnectionButton.outputJack = self.outputJackView;
//add action to button
[addConnectionButton addTarget:self action:#selector(addConnectionButtonTarget:) forControlEvents:UIControlEventTouchUpInside];
NSLog(#"output jack name before: %#", self.outputJackView.jackName);
[self.view addSubview:addConnectionButton];
NSLog(#"output jack name after: %#", self.outputJackView.jackName);
}
The two NSLog's at the end return the name correctly on the first one (before) and return null on the second (after). The jackName properties are NSString's. It's obvious that the property is being set to null after a subview is added, but I can't figure out why that would happen.
Here is the method from the class that launches the UIPopoverViewController in case it matters:
- (void)editCableConnectionsWith:(JackView *)outputJack
{
//launches the note menu popover
self.cableConnectionMenuController = [[CableConnectionMenuController alloc] init];
self.cableConnectionMenuController.delegate = (id)self;
//find appropriate connection to edit
for (JackView *currentJack in jackArray)
{
if (currentJack == outputJack)
{
//create temp array of input jacks to send to cable connection controller
NSMutableArray *inputButtonTempArray = [self returnInputJackArrayWithOutputJack:currentJack];
//set information for creating disconnect buttons in popover
[self.cableConnectionMenuController setButtonTextWithOutputJack:currentJack withInputArray:inputButtonTempArray];
}
}
self.editConnectionsPopoverController = [[UIPopoverController alloc] initWithContentViewController:self.cableConnectionMenuController];
[self.editConnectionsPopoverController presentPopoverFromRect:pulseRing.frame inView:self permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
How is the jackName property declared? My guess is that it's a weak reference.
I had a similar issue where a weak reference on a view was reset after the view was added as a subview. My understanding is that a weak reference should only be used when there's a potential retain cycle (e.g. you have a backpack with a reference to a calculator and the calculator points back to the backpack--see the Big Nerd Ranch book).
I'm not quite sure why that's an issue here, but I encountered something similar and figured I'd share.

Displaying an NSString on a Custom View

I have an interface that has an NSTextField, NSButton, and an NSView. When I type something in the NSTextfield and press the button, I want the text to be drawn in the NSView. So far I have everything connected and working, except for the view.
How can I connect the text and the view so that every time I press the button, the text is drawn to the view?
How can I connect the text and the view so that every time I press the button, the text is drawn to the view?
Views do their own drawing.
You need to give the view the string to draw, and then set the view as needing display. You'll do these in the action method that you wire the button up to.
First, your custom view class needs to have a property for the value (string, in this case) that it's going to display. From your action method, which should generally be on a controller object, send the view object a setFoo: message (assuming you named the property foo). That takes care of job one: The view now has the value to display.
Job two is even easier: Send the view a setNeedsDisplay: message, with the value YES.
That's it. The action method is two lines.
Of course, since views draw themselves, you also need your custom view to actually draw, so you need to implement the drawRect: method in that class. It, too, will be short; all you need to do is tell the string to draw itself.
Bindings
http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/CocoaBindings/Concepts/WhatAreBindings.html#//apple_ref/doc/uid/20002372-CJBEJBHH
For simplicity's sake I didn't mention this before, but the app also has a speech element to speak the string. This aspect of the program works fine, so just ignore any messages involving the SpeakAndDraw class (it's actually misnamed and only includes a speech method, nothing about drawing).
View.m
#import "View.h"
#implementation View
#synthesize stringToDraw;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setAttributes];
stringToDraw = #"Hola";
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
NSRect bounds = [self bounds];
[self drawStringInRect:bounds];
}
- (void)setAttributes
{
attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:[NSFont fontWithName:#"Helvetica"
size:75]
forKey:NSFontAttributeName];
[attributes setObject:[NSColor blackColor]
forKey:NSForegroundColorAttributeName];
}
- (void)drawStringInRect:(NSRect)rect
{
NSSize strSize = [stringToDraw sizeWithAttributes:attributes];
NSPoint strOrigin;
strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
[stringToDraw drawAtPoint:strOrigin withAttributes:attributes];
}
#end
SpeakerController.m
#import "SpeakerController.h"
#implementation SpeakerController
- (id)init
{
speakAndDraw = [[SpeakAndDraw alloc] init];
view = [[View alloc] init];
[mainWindow setContentView:mainContentView];
[mainContentView addSubview:view];
return self;
}
- (IBAction)speakText:(id)sender
{
[speakAndDraw setStringToSay:[text stringValue]];
[speakAndDraw speak];
[view setStringToDraw:[text stringValue]];
[view setNeedsDisplay:YES];
NSLog(#"%#", view.stringToDraw);
NSLog(#"%#", [view window]);
}
#end