You know Visual Studio, that awesome element called "ListBox"? Just a box that would list a bunch of strings.
I am now working with XCode, and I found this class in the interface builder "NSScrollView". It seems to be able to list me a couple strings. It says it got a NSTextView inside, but, how do I access it?
I am not even sure if NSScrollView is the correct solution I need, but if I could simply access the NSTextView inside it, I think it would be enough.
See NSTableView.
As for getting to a text view inside a scroll view, create an Interface Builder outlet (IBOutlet) and connect it to the text view itself, rather than the scroll view.
To get to a text view inside a scroll view; you need to select the controller with your outlet defined; click and hold control and then drag the blue connection line from your controller to the top line of the scroll view; then just wait for a blue line to appear; this will then prompt to let you link your outlet to the text view.
Josh's answer above to use NSTableView is correct. For those not that familiar with it, it can seem like a much bigger task than it actually turns out to be. Hopefully this saves people some time.
Rather than fight with NSTableCellView assumptions, you can create any type of simple view you want and use auto layout (or even return a simple NSTextView. This is what I did to get more control over layout of my text strings:
#interface PreferenceTableViewCell : NSView
#property (nonnull, strong, readonly) NSTextField *tf;
#end
#implementation PreferenceTableViewCell
-(id)init
{
self = [super init];
if(self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
self.autoresizesSubviews = YES;
_tf = [NSTextField labelWithString:#""];
_ tf.translatesAutoresizingMaskIntoConstraints = NO;
_tf.autoresizesSubviews = YES;
[self addSubview:_tf];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[_tf]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_tf)]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:_tf attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}
return self;
}
#end
Then put this whereever you need the list of strings (or controls, or whatever):
_tv = [NSTableView new];
_tv.translatesAutoresizingMaskIntoConstraints = NO;
_tv.autoresizesSubviews = YES;
_tv.focusRingType = NSFocusRingTypeNone;
_tv.delegate = self;
_tv.dataSource = self;
_tv.rowHeight = 40; // Use this to adjust the height of your cell or do it in cell.
_tv.headerView = nil;
_tv.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular;
_tv.allowsColumnReordering = NO;
_tv.allowsColumnResizing = NO;
_tv.allowsEmptySelection = NO;
_tv.allowsTypeSelect = NO;
_tv.gridStyleMask = NSTableViewGridNone;
[panel addSubview:_tv];
// TableView Column
NSTableColumn *col1 = [[NSTableColumn alloc] initWithIdentifier:#"c1"];
col1.resizingMask = NSTableColumnAutoresizingMask;
[_tv addTableColumn:col1];
Then in whatever is set as the delegate and datasource for the NSTableView add these methods:
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tv
{
return stringArray.count;
}
-(NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)tc row:(NSInteger)row
{
// This can be ANY NSView based control built as shown above.
PreferenceTableViewCell *cell = [PreferenceTableViewCell new];
cell.tf.stringValue = stringArray[row];
return cell;
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification
{
// Code to do whatever when a list item is selected.
}
That is basically it for a simple list. See the Apple Docs on NSTableView for more details on how to bind the table to a data sources and more complicated problems.
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 have a static table view controller. Within some of the cells, I have text boxes. I would like to enable or disable all the text boxes in one go. I know I could do something like
self.nameTextField.Enabled = NO;
self.ageTextField.Enabled = NO;
self.hairColorTextField.Enabled = NO;
But there has to be something more elegant. Something like
for (UIControl* control in self.allChildControls) { // <-- I totally just made that up.
if ([control isKindOfClass:[UITextField class]]) {
control.Enabled = NO;
}
}
I don't think I am asking the right question...
You can use the subviews property od UIView. It contains all child UI elements.
#property(nonatomic, readonly, copy) NSArray *subviews
UIView Documentation
for (UIView *subview in self.view.subviews) {
//check by class or tag
}
If you have a static tableviewController, I am assuming you aren't allowing the user to add/delete cells. If this is the case, your question is simple. You just need to add an outlet to each of the UITextField objects and toggle it's userInteractionEnabled property to no.
self.myTextField.userInteractionEnabled = NO;
self.mySecondTextField.userInteractionEnabled = NO;
Hope this helps :)
Is there a way to change MKAnnotationView style (like from red label with number to green colored label with number).
I want to change this style according to distance from target. My annotation is moving, with user.
I dont want to use remove / add annotation, because it causes "blinking".
Can it be done someway?
UPDATE:
I am adding code, how I am doing it right now
MKAnnotationView *av = [mapView viewForAnnotation:an];
if ([data->type isMemberOfClass:[UserAnnotationImage class]])
{
UIImage *img = [UIImage imageNamed: ((UserAnnotationImage *)data->type)->url];
[av setImage:img];
}
else if ([data->type isMemberOfClass:[UserAnnotationLabel class]])
{
UIView * v = [av viewWithTag:0];
v = ((UserAnnotationLabel *)data->type)->lbl;
av.frame = ((UserAnnotationLabel *)data->type)->lbl.frame;
}
else if ([data->type isMemberOfClass:[UserAnnotationView class]])
{
UIView * v = [av viewWithTag:0];
v = ((UserAnnotationView *)data->type)->view;
av.frame = ((UserAnnotationView *)data->type)->view.frame;
}
Sadly, its not working :(
Yes, basically you get a reference to the annotation view and update its contents directly.
Another way, if you have a custom annotation view class, is to have the annotation view monitor the changes it is interested in (or have something outside tell it) and update itself.
The first approach is simpler if you are using a plain MKAnnotationView or MKPinAnnotationView.
Wherever you detect that a change to the view is needed, get a reference to the view by calling the map view's viewForAnnotation instance method. This is not the same as calling the viewForAnnotation delegate method.
Once you have a reference to the view, you can modify as needed and the changes should appear immediately.
An important point is that the logic you use to update the view outside the delegate method and the logic you have in the viewForAnnotation delegate method must match. This is because the delegate method may get called later (after you've updated the view manually) by the map view and when it does, the code there should take the updated data into account.
The best way to do that is to have the annotation view construction code in a common method called both by the delegate method and where you update the view manually.
See change UIImage from MKAnnotation in the MKMapView for an example that updates just the annotation view's image.
For an example (mostly an idea for an approach) of updating the view using a custom annotation view class, see iPad Mapkit - Change title of "Current Location" which updates the view's pin color periodically (green, purple, red, green, purple, red, etc).
There are too many unknowns in your code to explain why it doesn't work. For example:
What is data? Is it annotation-specific (is it related to an)? What is type? Does it change after the annotation has been added to the map?
Why is data storing entire view objects like a UILabel or UIView instead of just the underlying data that you want to show in those views?
imageNamed requires the image to be a resource in the project (not any arbitrary url)
Don't use a tag of 0 (that's the default for all views). Start numbering from 1.
You get a view using viewWithTag but then replace it immediately with another view.
I'll instead give a more detailed but simple(r) example...
Assume you have an annotation class (the one that implements MKAnnotation) with these properties (in addition to coordinate, title, and subtitle):
#property (nonatomic, assign) BOOL haveImage;
#property (nonatomic, copy) NSString *labelText;
#property (nonatomic, copy) NSString *imageName;
#property (nonatomic, assign) CLLocationDistance distanceFromTarget;
To address the "important point" mentioned above (that the viewForAnnotation delegate method and the view-update-code should use the same logic), we'll create a method that is passed an annotation view and configures it as needed based on the annotation's properties. This method will then be called both by the viewForAnnotation delegate method and the code that manually updates the view when the annotation's properties change.
In this example, I made it so that the annotation view shows the image if haveImage is YES otherwise it shows the label. Additionally, the label's background color is based on distanceFromTarget:
-(void)configureAnnotationView:(MKAnnotationView *)av
{
MyAnnotationClass *myAnn = (MyAnnotationClass *)av.annotation;
UILabel *labelView = (UILabel *)[av viewWithTag:1];
if (myAnn.haveImage)
{
//show image and remove label...
av.image = [UIImage imageNamed:myAnn.imageName];
[labelView removeFromSuperview];
}
else
{
//remove image and show label...
av.image = nil;
if (labelView == nil)
{
//create and add label...
labelView = [[[UILabel alloc]
initWithFrame:CGRectMake(0, 0, 50, 30)] autorelease];
labelView.tag = 1;
labelView.textColor = [UIColor whiteColor];
[av addSubview:labelView];
}
if (myAnn.distanceFromTarget > 100)
labelView.backgroundColor = [UIColor redColor];
else
labelView.backgroundColor = [UIColor greenColor];
labelView.text = myAnn.labelText;
}
}
The viewForAnnotation delegate method would look like this:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MyAnnotationClass class]])
{
static NSString *myAnnId = #"myann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:myAnnId];
if (av == nil)
{
av = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:myAnnId] autorelease];
}
else
{
av.annotation = annotation;
}
[self configureAnnotationView:av];
return av;
}
return nil;
}
Finally, the place where the annotation's properties change and where you want to update the annotation view, the code would look something like this:
ann.coordinate = someNewCoordinate;
ann.distanceFromTarget = theDistanceFromTarget;
ann.labelText = someNewText;
ann.haveImage = YES or NO;
ann.imageName = someImageName;
MKAnnotationView *av = [mapView viewForAnnotation:ann];
[self configureAnnotationView:av];
I have a very custom table view that actually serves as a content view, but table view was the obvious choice. I have a section index that i use to scroll the TableView - but there are no sections (well, one is there obviously). For the purpose of the user's orientation, I'd like to fade a view over the table view that is semi-transparent and shows a text in there. It should look like the overlay with the letters when scrolling the new iPod nano's section index. I don't know where i should put the code - because my view has to disappear sometime again too, and I don't really wanna use notifications. I'd init the view inside the tableview: sectionForSectionIndexTitle method. Thanks in advance.
Create a property in your .h file
#property (nonatomic, retain) UILabel *overlayLabel;
And add the following code to your .m file
- (void)viewDidLoad {
[super viewDidLoad];
self.overlayLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0.0f,
0.0f,
self.tableView.frame.size.width,
self.tableView.frame.size.height)] autorelease];
overlayLabel.backgroundColor = [UIColor clearColor];
overlayLabel.alpha = .5f;
overlayLabel.textAlignment = UITextAlignmentCenter;
overlayLabel.text = #"Some text";
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView addSubview:overlayLabel];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[overlayLabel removeFromSuperview];
}
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