How do I access the properties of UITextField's selectedTextRange UITextPosition objects? - objective-c

I'm trying to establish the start and end positions of a text selection of a UITextField instance, using its selectedTextRange property (as gained from the UITextInput protocol). However, I have no idea how to access the properties of the UITextPosition objects that make up the start and end properties of selectedTextRange.
Apple's docs on UITextPosition are woeful at this time, providing no methods or properties, though I know there are such properties in the object, because NSLogging one gives this:
<UITextPositionImpl: 0x6aaeb60>
<<WebVisiblePosition: 0x6aa40e0>(offset=5, context=([s|a], [u+0073|u+0061])>
In this example, the 'offset' is correct, and the context shows the characters either side of the selection point ('s' and 'a'), but I don't know how to access this nebulous WebVisiblePosition class. So, in short, is there a way of retrieving the details I want using UITextPosition objects from UITextField?

Of course, just after asking my question I found the answer, in this SO question: UITextPosition in UITextField.
It seems that when used as part of UITextField, the UITextPosition objects are not meant to be tinkered with directly, but used to feed other methods. In this case, the method offsetFromPosition:toPosition:, along with the text field property beginningOfDocument, can be used to return an NSInteger of a selection index.

Related

Where to define NSArray and where to define Button action?

I have made an array of text strings and want to pull these out an into a label by EITHER swiping of pressing a button. So i have two different functions/methods, the button and the swipe method.
Where and how do I define the array so that these methods can refer to it? Should it be a 'extern NSArray' ?
I have uploaded the image of full code externally http://s1.postimg.org/b2e3m4v67/Sk_rmbillede_2014_05_11_kl_15_48_28.png
Not sure though if that's a violation of some rules here(?)
You want the quote to change on swipe/button press.
In your button press/swipe methods you're setting the text property of the VC's label property to something called Quoteselected. And it looks like Quoteselected is a random element of the array Quotes - or at least maybe it is, since that random number could be 6-10, and you don't have any objects in the Quotes array at those indices - so if those numbers are ever generated by the random function, your program will crash due to an index out of bounds error.
What you probably want to do is generate a new random number on each user interaction and then at that point change the value of Quoteselected to be the object at that index of the array. And then assign that to the label's text property.
As far as defining the Array - I wouldn't have done it the way you did. What you've got there is an "ivar", an instance variable. On iOS, those are typically properties. And since it's a "private" array that outside classes won't need to know about, I'd declare it as a part of the class extension.
So,
#interface BOViewController()
#property NSArray *quotes;
#end
Also note my capitalization changes, that's better style.
So now you've got an array property declared, but there's no data in it. It depends on how you created your View Controller instance. Assuming you did it in a storyboard, it would go in awakeFromNib: or viewDidLoad: (if you instantiated the VC automatically, you might put it in the initWithNibName: method).
- (void)viewDidLoad {
[super viewDidLoad];
self.quotes = #[#"Test", #"Number 3"...];
Then when you want to reference the array in other parts of the class:
self.label.text = self.quotes[0];
Note that your existing code should work, it's just not typical Cocoa coding style.

How do we implement selectionRectsForRange: from UITextInput Protocol?

How do we implement selectionRectsForRange: from UITextInput Protocol ?
Has anybody figured out this one?
Is it just very dependent upon specific use-case needs? Or is there something in the frameworks that will call this method?
To silence the compiler it is of course appropriate to stub out the method, but will returning nil or an empty NSArray cause any harm?
According to session 220 at WWDC12 this method was added to support subclassing of UITextView where the implementation renders its own text. Sadly their sample code from that session isn't available, would love to peek at it to see if I've missed anything in my implementation.
It's fairly similar to how you'd implement -firstRectForRange: except you'd return all rects which covers the current selection.
Furthermore you'd have to subclass UITextSelectionRect (it's an abstract class like UITextPosition/UITextRange) which you'd return an array of from this method. Make sure to calculate the containsStart and containsEnd properties correctly and only return YES for one of each once across all the selection rects you return. These properties are used by UITextView to decide on where to place the selection resize "paddles".
Returning an empty array (or nil I suppose) would indicate that UITextView shouldn't draw any selection rects for the current selection.

Bindings vs IBOutlets in Objective-C/Cocoa

I'm going through a simple Objective-C/Cocoa program to try and learn the language and am getting a little confused about how some things are linked from the code I write to the interface builder.
For example, I have a simple NSString:
#property (assign) NSString *letters;
And in my interface builder, I have a text field and I use the text field's bindings to connect it to letters.
However, the example also has this:
#property (assign) IBOutlet NSArrayController *wordsController;
In the view I have a table that continuously changes and shows different words and those words are stored in an NSMutableArray. I suppose I can understand that I just can't bind the array to the the table because there are some more complexities. So in the Interface Builder I create an Array Controller and bind it to the table. In the Array Controller's bindings, I bind the Array Controller to the array of words.
I understand that the last thing I have to do is also bind the Array Controller to my NSArrayController object as well. I don't understand why I do this through the main controller object by making a connection between this outlet and the wordsController. In the Array Controller's bindings section there's a greyed out option, Content Object, which says "An NSArrayController that the NSArrayController treats as its content." Why wouldn't I set the binding here? What is the significance of it being an outlet and why is it different than my NSString letters?
Thanks
You are confusing bindings and IBOutlets. This is not unreasonable -- it's a lot of Control-dragging of connections and it can be hard to keep clear what's going on. Let me try to explain:
Bindings are a way to let Cocoa handle the mechanics of keeping a model (some collection of data, even something as simple as a single NSString) and a view (an object which displays on the screen) in sync. When you "bind" your NSString to a text field's value, you are asking the framework to communicate changes to the string or the text field "behind the scenes"; your object which owns the string gets notified to change the string's value when the text field changes, and vice versa.*
A similar situation applies to your mutable array, array controller, and table view. You're essentially right about the complications: the mutable array and the table view don't know how to talk to each other; the array controller stands in between and facilitates: ("Okay, tableView wants to know what to put in row i. Array, give me your object at index i." :) In the past, you would've had to write that code manually, and it looked very similar every time you did so.
That's what the bindings do. They are a way to reduce boilerplate code. The IBOutlet to the array controller gives your object a way to send messages to the array controller, if necessary. One simple example of why you might need to do this is to allow a menu item to trigger a method in the array controller; a document object or another controller might handle the action from the menu item and call the appropriate message on the array controller. You can also ask the array controller for its arrangedObjects to get, for example, the sorted and filtered version of its content array.
* One side note here is that your NSString property should almost certainly use retain, not assign. The object that contains this variable should be responsible for its memory.

Bind custom control to NSArrayController

I've subclassed NSTextField to create a custom control, and I want to bind a property (which is an NSArray) of my custom control to an NSArrayController. However I don't know how to propagate the array from my control to the NSArrayController. The key-path I'm using on the NSArrayController is arrangedObjects.name. For example, if I'm trying to propagate the array (#"One", #"Two", #"Three") and I simply use:
[boundObject setValue:myArray forKeyPath:#"arrangedObjects.name"]
it will set the value of each element of arrangedObjects.name to the array (#"One", #"Two", #"Three"). What I want to happen is to have the first element in arrangedObjects.name set to #"One", the second value set to #"Two", etc.
NSTableColumn does this, so I know it's possible, but I can't figure out how it is implemented.
What's the best way to achieve this?
Ok, well I think I may have answered my own question. I won't mark it as the correct answer right away in case someone can tell me a better way to do this. This way is pretty much a hack.
Anyway, I was reading about NSArrayController here: http://www.cocoadev.com/index.pl?NSArrayController which says:
If valueForKeyPath:#"arrangedObjects.name" is sent to an array controller, one (as expected) gets back an array of name values, but if another object replicates this behaviour, and the table column's value is instead bound to this object, it will display the entire array for each row.
and
You bind the value of an NSTableColumn to arrangedObjects.someKey. If you try to programmatically invoke valueForKeyPath:#"arrangedObjects.someKey" on your array controller, you'll get back the array resulting from invoking valueForKey:#"someKey" on the arrangedObjects array -- this is all fine. So one would think that NSTableColumn's value can also be bound to my objects someArray.someKey, but this will not work (on several levels).
So basically it sounds like NSTableColumn special cases its bindings for NSArrayController and arrangedObjects which is why it works the right way, and my custom control doesn't.
This doesn't seem very flexible, but it's the only way I can find to make it actually work. I implemented a special case in my bindings for NSArrayController and arrangedObjects and I was able to make it work like I wanted.
A better solution would be much appreciated, though!

NSComboBox - Obtaining selected information and NSComboBoxDataSource

For the life of me, I am being continually stumped with NSComboBox.
I created an object that conforms the NSComboBoxDataSource protocol, and implemented:
- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox;
- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index;
I set the instance of my NSComboBox to use a Data Source, and set this object as the source. That works great, my implementation returns the number of items, and returns an NSString value for an item at specific indices.
Then I decide that I want to do something when something is selected, this is where my problems begin. There is no obvious method to override in the NSComboBoxDataSource protocol to handle the selection of items in the combo box.
So, I also have my object conform to NSComboBoxDelegate and implement:
- (void)comboBoxSelectionDidChange:(NSNotification *)notification;
Unfortunately, unlike NSTableView on selection, the notification's object is the NSComboBox not the object of the item selected. "Fine" I tell myself, I will call the NSComboBox method:
- (id)objectValueOfSelectedItem;
This should return the item that is selected and I can go from there. However, that method is to be called ONLY when usesDataSource is set to NO, which is not my case. Warnings start flying when I use this.
So, my question is, what is the proper way to handle NSComboBox selections when you are using a data source?
I think you want indexOfSelectedItem instead of objectValueOfSelectedItem. Then since you're the data source you should be able to call your own comboBox:objectValueForItemAtIndex: method.