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.
Related
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.
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
I am hoping to "automate" a click on the segmentController with the index of 0.
My tabBar-based app has multiple segmentControllers in a tab in the ViewDidAppear method, I would like to automatically have it "click" the first segmented controller.
if (segmentController.selectedSegmentIndex == 0) {
//stuff here
}
if (segmentController.selectedSegmentIndex == 1) {
//stuff here
}
Does anyone know how I might accomplish this? Thank you!
If you're creating it programmatically, you could lazy load it like this:
#interface ExampleViewController : UIViewController
#property (nonatomic, strong) UISegmentedControl *segmentedControl;
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl;
#end
#implementation ExampleViewController
- (UISegmentedControl *)segmentedControl
{
if (!_segmentedControl)
{
NSArray *items = #[#"First", #"Second", #"Third"];
_segmentedControl = [[UISegmentedControl alloc] initWithItems:items];
[_segmentedControl addTarget:self
action:#selector(segmentedControlClicked:)
forControlEvents:UIControlEventValueChanged];
[_segmentedControl setSelectedSegmentIndex:0]; // Set Default selection
CGRect frame = _segmentedControl.frame;
frame.origin = CGPointMake(0.0f, 0.0f); // Move to wherever you need it
[self.view addSubview:_segmentedControl];
}
return _segmentedControl;
}
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl
{
// Whatever your code is goes here...
}
#end
If you're wanting a method to be called also initially, you can call it within your viewDidLoad: method as such:
- (void)viewDidLoad
{
[self.segmentedControl setSelectedSegmentIndex:0]; // Set desired default index (optional if set in lazy load as shown above)
[self segmentedControlClicked:self.segmentedControl];
}
This would hence simulate a click on desired default index.
Be careful putting the above into viewDidAppear: (you could if you really wanted to) because anytime the view comes to the front, this method will be called (in example, if this view controller presents a modal view controller, once the modal is dismissed, this view controller's viewDidAppear: method will be called).
Cheers!
Set the selectedSegmentIndex property on your UISegmentedControl in your viewDidAppear (or viewDidLoad) method.
self.segmentedController.selectedSegemntIndex = 1;
UISegmentedControl Reference
I have the following situation. I have a view controller (ContentController) with 3 buttons at the top. Below I added a second view (contentView), this view should display content that are behind those 3 buttons. So every time I press a button, the contentView changes. So what I did was created three addition controllers.
FirstController.m, FirstController.h, FirstController.xib
SecondController.m, SecondController.h, SecondController.xib,
ThirdController.m, ThirdController.h, ThirdController.xib,
in my ContentController.m I added 3 IBActions that handles the change.
#interface ViewController ()
#property (nonatomic,strong) UIViewController *nextController;
#end
#implementation ViewController
#synthesize nextController = _nextController;
-(IBAction)chooseFirstController:(id)sender {
_nextController = [[FirstController alloc] initWithNibName:#"FirstController" bundle:nil];
[self.contentView addSubview:_nextController.view];
}
-(IBAction)chooseSecondController:(id)sender{
_nextController = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.contentView addSubview:_nextController.view];
}
-(IBAction)chooseThirdController:(id)sender{
_nextController = [[ThirdViewController alloc] initWithNibName:#"ThirdViewController" bundle:nil];
[self.contentView addSubview:_nextController.view];
}
My first and ThirdController just showing tableviews inside the contentView of contentController. But in my secondController I used a gridView for displaying items. Now when I press a cell, it should go to a detailViewController, whichs shows more information about that cell. So in my cellForRowAtIndex I did the following.
- (void)gridView:(NRGridView *)gridView didSelectCellAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cell clicked");
PlayerDetailController *playerController = [[PlayerDetailController alloc]initWithNibName:#"PlayerDetailController" bundle:nil];
[playerController setLblTest:#"hooray this works"];
[self.view addSubview:playerController.view];
}
It correctly executes setLblTest and also showing the detailViewcontroller inside my ContentView. But it don't changes the label. to my #"hooray" this works. But although as you can see in my log it passes it correctly.
#synthesize testLabel = _testLabel;
#synthesize lblTest = _lblTest;
-(void)setLblTest:(NSString *)lblTest{
if(![_lblTest isEqual:lblTest]){
_lblTest = lblTest;
}
NSLog(#"log komt %#",lblTest);
[_testLabel setText:lblTest];
}
LOG
RacingGenk[567:c07] log komt hooray this works
Hope this clarifies more my problem
Thank you
To get the label to update, you'll need to change your code to look like this.
self.testLabel.text = lblTest;
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];
}