I know that javascript, for example supports functions inside of functions, like so:
function doSomething(){
function doAnothingThing(){
//this function is redefined every time doSomething() is called and only exists inside doSomething()
}
//you can also stick it inside of conditions
if(yes){
function doSomethingElse(){
//this function only exists if yes is true
}
}
}
Does objective-c support this? Theoretical example:
-(void) doSomething:(id) sender{
-(void) respondToEvent: (id) sender{
//theoretically? ... please?
}
}
BONUS: What is the proper term for a "local" function?
A bit late, but you can place an inline block into a function, which kind of acts like your nested function questions.
-(int)addNumbers:(int)a withB:(int)b withC:(int)c {
// inline block
int(^inlineaddNumbers)(int, int) = ^(int a, int b) {
return a + b;
};
if( a == 0 ) return inlineaddNumbers(b,c);
else return inlineaddNumbers(a,c);
}
It's a bit messy, but it works!
The usual term is nested function. gcc supports nested functions as an extension to C (disabled by default). I don't think this option is available with Objective-C (or C++) with gcc though, and even if it were it's probably not a good idea to use it (portability etc).
gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html
By default Xcode disallows nested functions.
If you want to switch them on, open up the Info for your project, go to the Build tab, and set "Other C flags" (under the section titled "GCC 4.2 - Language") to "-fnested-functions".
(This is stored in your project.pbxproj file as "OTHER_CFLAGS = "-fnested-functions";"
Expanding the answer provided by Gui13 a little bit, with object parameters.
The following code snippet demonstrates how to draw an 11x5 set of UILabels.
// inline block - to be called as a private function
UILabel *(^createLabel)(CGRect, NSString *, UIColor *) = ^UILabel *(CGRect rect, NSString *txt, UIColor *color) {
UILabel *lbl = [UILabel new];
lbl.frame = rect;
lbl.textColor = color;
lbl.backgroundColor = [UIColor whiteColor];
lbl.font = [UIFont systemFontOfSize:30.f];
lbl.textAlignment = NSTextAlignmentCenter;
lbl.text = txt;
return lbl;
};
// loop to create 11 rows of 5 columns over the whole screen
float w = CGRectGetWidth([UIScreen mainScreen].bounds);
float h = CGRectGetHeight([UIScreen mainScreen].bounds);
float top = h / 10; //start at 10% from top
float vOffset = h / 13; //space between rows: 7.6% of screen height
NSArray *xFrom, *xTo; //columns to start at 5%, 20%, 40%, 60%, 80%
xFrom = #[#(1.f/20), #(1.f/5), #(2.f/5), #(3.f/5), #(4.f/5)];
xTo = #[#(1.f/5-1.f/16), #(2.f/5-1.f/16), #(3.f/5-1.f/16), #(4.f/5-1.f/16), #(19.f/20)];
#define SFMT(format...) [NSString stringWithFormat:format]
for (int row=0; row<11; row++) {
for (int col=0; col<5; col++) {
CGRect rect = CGRectMake([xFrom[col] floatValue]*w, top+row*vOffset, [xTo[col] floatValue]*w-[xFrom[col] floatValue]*w, vOffset*0.9);
UILabel *lbl = createLabel(rect, SFMT(#"%i-%i", row, col), [UIColor blueColor]);
[<my-view> addSubview:lbl];
}
}
Here is the output for this code:
#Moshe You cannot actually provide the nested functions inside the Objective C. Instead you can use the feature in latest Swift 3 which enables this feature. It will be like below:
func someFunction(input:String)->String
{
var inputString = input;
func complexFunctionOnString()
{
inputString = "Hello" + input;
}
complexFunctionOnString();
return inputString;
}
someFunction("User");
Related
I want to be able to update two sets of identical buttons with one function. Also I don't want to update all the buttons, only some of them.
Can I have a function like this?:
-(void) updateFields{
updateButton1 : (Bool) x
updateButton2 : (Bool) y
updateButton3 : (Bool) z }
The implementation will look like this:
[button1_1 setEnabled:x];
[button1_2 setEnabled:x]; //called only if updateButton1 is given an argument
[button2_1 setEnabled:y];
etc...
What about passing an array of button and an array of boolean wrapped in a NSNumber?
- (void)updateButton:(NSArray *)buttons withArray:(NSArray *)enablers {
// buttons is an array of UIButton
// enablers is an array of NSNumber created from boolean value
// Security check
if(buttons.count != enabler.count) {
NSLog(#"Error: array have different dimensions");
return;
}
// Enable buttons
for(int i=0; i<buttons.count; i++) {
UIButton *button = (UIButton *)[buttons objectAtIndex:i];
BOOL enable = [[enablers objectAtIndex:i] boolValue]
[button setEnabled:enable];
}
}
This may not be possible with primitive data types unless you create objects from them and put them in NSArray or NSDictionary. Other option can be to create a custom object and pass that as argument.
- (void)selectButton:(SelectedButton *)iButton {
if (iButton.type = A) {
// Handle A
} else if (iButton.type = B) {
// Handle B
} else if (iButton.type = C) {
// Handle C
}
}
i think the syntax you're going for makes more sense as a C function
however note that in this example the parameters are NOT optional.
void updateButtons(BOOL btn1, BOOL btn2, BOOL btn3){
button1.enabled = btn1
button2.enabled = btn2
button3.enabled = btn3
}
It's possible to create an Objective-C method with a variable argument list, as Matt Gallagher explains in Variable argument lists in Cocoa. Variable argument lists are even used in the Foundation framework, e.g. +[NSArray arrayWithObjects:...].
That said, it's probably a lot less work to pass the list of buttons in your method as an array, particularly given the ease with which one can now create arrays using object literals:
[foo updateFields:#[button1, button2, button3]];
I found similar questions, but -containsObject is not working like I expect.
My problem is the NSMutableArray -containsObject method returns true when it shouldn't,
when trying to generate random UNIQUE colors and add to an array.
What is the best way to check if NSMutableArray contains an object with same values.
NSMutableArray *color_arr=[NSMutableArray array];
UIColor *t;
for(int i=0; i<100; i+=1)
{
int r = arc4random()%256;
int g = arc4random()%256;
int b = arc4random()%256;
t=[UIColor colorWithRed:r green:g blue:b alpha:255];
if (![color_arr containsObject:t])
[color_arr addObject:t];
//[t release];//is t need to be released here on non-arc project? well Im not sure.
}
NSLog(#"total:%d",[color_arr count]);
The NSLog() always says array count is 1.
New Edit:
The structure of your for() loop is wrong too. You are declaring the UIColor before the loop begins. You should be declaring the color AFTER the loop begins:
for (i=0;i<100;i++) {
int rInt = arc4random()%256;
float rFloat = (float)rInt/255.0f;
//same with gInt, bInt
//make gFloat and bFloat this way
UIColor *t = [UIColor colorWithRed:rFloat green:gFloat blue:bFloat alpha:1];
if (![color_arr containsObject:t]) {
[color_arr addObject:t];
}
NSLog(#"%i",color_arr.count);
}
UIColor doesn't use integer values, it uses float values. Try dividing your integer by 255 and then setting those as r, g, b.
Like:
int rInt = arc4random()%256;
float rFloat = (float)rInt/255.0f;
//same with gInt, bInt
//make gFloat and bFloat this way
t = [UIColor colorWithRed:rFloat green:gFloat blue:bFloat alpha:1];
I need to find the pixel-frame for different ranges in a textview. I'm using the - (CGRect)firstRectForRange:(UITextRange *)range; to do it. However I can't find out how to actually create a UITextRange.
Basically this is what I'm looking for:
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST
CGRect rect = [textView firstRectForRange:range2];
return rect;
}
Apple says one has to subclass UITextRange and UITextPosition in order to adopt the UITextInput protocol. I don't do that, but I tried anyway, following the doc's example code and passing the subclass to firstRectForRange which resulted in crashing.
If there is a easier way of adding different colored UILables to a textview, please tell me. I have tried using UIWebView with content editable set to TRUE, but I'm not fond of communicating with JS, and coloring is the only thing I need.
Thanks in advance.
You can create a text range with the method textRangeFromPosition:toPosition. This method requires two positions, so you need to compute the positions for the start and the end of your range. That is done with the method positionFromPosition:offset, which returns a position from another position and a character offset.
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
{
UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
CGRect rect = [textView firstRectForRange:textRange];
return [textView convertRect:rect fromView:textView.textInputView];
}
It is a bit ridiculous that seems to be so complicated.
A simple "workaround" would be to select the range (accepts NSRange) and then read the selectedTextRange (returns UITextRange):
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
textView.selectedRange = range;
UITextRange *textRange = [textView selectedTextRange];
CGRect rect = [textView firstRectForRange:textRange];
return rect;
}
This worked for me even if the textView is not first responder.
If you don't want the selection to persist, you can either reset the selectedRange:
textView.selectedRange = NSMakeRange(0, 0);
...or save the current selection and restore it afterwards
NSRange oldRange = textView.selectedRange;
// do something
// then check if the range is still valid and
textView.selectedRange = oldRange;
Swift 4 of Andrew Schreiber's answer for easy copy/paste
extension NSRange {
func toTextRange(textInput:UITextInput) -> UITextRange? {
if let rangeStart = textInput.position(from: textInput.beginningOfDocument, offset: location),
let rangeEnd = textInput.position(from: rangeStart, offset: length) {
return textInput.textRange(from: rangeStart, to: rangeEnd)
}
return nil
}
}
To the title question, here is a Swift 2 extension that creates a UITextRange from an NSRange.
The only initializer for UITextRange is a instance method on the UITextInput protocol, thus the extension also requires you pass in UITextInput such as UITextField or UITextView.
extension NSRange {
func toTextRange(textInput textInput:UITextInput) -> UITextRange? {
if let rangeStart = textInput.positionFromPosition(textInput.beginningOfDocument, offset: location),
rangeEnd = textInput.positionFromPosition(rangeStart, offset: length) {
return textInput.textRangeFromPosition(rangeStart, toPosition: rangeEnd)
}
return nil
}
}
Swift 4 of Nicolas Bachschmidt's answer as an UITextView extension using swifty Range<String.Index> instead of NSRange:
extension UITextView {
func frame(ofTextRange range: Range<String.Index>?) -> CGRect? {
guard let range = range else { return nil }
let length = range.upperBound.encodedOffset-range.lowerBound.encodedOffset
guard
let start = position(from: beginningOfDocument, offset: range.lowerBound.encodedOffset),
let end = position(from: start, offset: length),
let txtRange = textRange(from: start, to: end)
else { return nil }
let rect = self.firstRect(for: txtRange)
return self.convert(rect, to: textInputView)
}
}
Possible use:
guard let rect = textView.frame(ofTextRange: text.range(of: "awesome")) else { return }
let awesomeView = UIView()
awesomeView.frame = rect.insetBy(dx: -3.0, dy: 0)
awesomeView.layer.borderColor = UIColor.black.cgColor
awesomeView.layer.borderWidth = 1.0
awesomeView.layer.cornerRadius = 3
self.view.insertSubview(awesomeView, belowSubview: textView)
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
UITextRange *textRange = [[textView _inputController] _textRangeFromNSRange:range]; // Private
CGRect rect = [textView firstRectForRange:textRange];
return rect;
}
Here is explain.
A UITextRange object represents a range of characters in a text
container; in other words, it identifies a starting index and an
ending index in string backing a text-entry object.
Classes that adopt the UITextInput protocol must create custom
UITextRange objects for representing ranges within the text managed by
the class. The starting and ending indexes of the range are
represented by UITextPosition objects. The text system uses both
UITextRange and UITextPosition objects for communicating text-layout
information. There are two reasons for using objects for text ranges
rather than primitive types such as NSRange:
Some documents contain nested elements (for example, HTML tags and
embedded objects) and you need to track both absolute position and
position in the visible text.
The WebKit framework, which the iPhone text system is based on,
requires that text indexes and offsets be represented by objects.
If you adopt the UITextInput protocol, you must create a custom
UITextRange subclass as well as a custom UITextPosition subclass.
For example like in those sources
I want to make an method which takes an CGFloat by reference.
Could I do something like this?
- (void)doStuff:(CGFloat*)floatPointer
I guess this must look different than other object pointers which have two of those stars. Also I'm not sure if I must do something like:
- (void)doStuff:(const CGFloat*)floatPointer
And of course, no idea how to assign an CGFloat value to that floatPointer. Maybe &floatPointer = 5.0f; ?
Could someone give some examples and explain these? Would be great!
objective-c is still c, so
-(void) doStuff (CGFloat *) f
{
*f = 1.2;
}
call with
CGFloat f = 1.0;
[self doStuff:&f];
If you are passing a CGFloat by reference, then accessing it is simple:
- (void)doStuff:(CGFloat*)floatPointer {
*floatPointer = 5.0f;
}
Explanation: as you are getting a reference, you need to de-reference the pointer (with the *) to get or set the value.
if you (hate pointers and ;-) prefer objective-c++ pass by reference, the following is an alternative:
-(void) doStuffPlusPlus:(CGFloat &) f
{
f = 1.3;
}
call by
CGFloat abc = 1.0;
[self doStuffPlusPlus:abc];
and, you need to rename the source filename from ???.m to ???.mm
I have an interface that has large numbers of controls, see image below.
Interface http://www.richardstelling.com/hosted/cocoainterface.png
What is the best way to access these, creating 288 IBOutlets in my AppController class and linking them all up seems inefficient.
I looked at forms, but they seemed to simplistic.
This is a proof-of-concept and will not ship so I'm open to any ideas. One caveat however I have to use Objective-C as the final product will be written in Objective-C/Cocoa.
NB:
The interface is static
Smaller boxed will hold integers (0-255)
You should look into NSMatrix. This is exactly what it's designed to solve.
NSTableView looks like the UI you need. The visual rendering will be a bit different but it will look more 'Mac'.
Either NSMatrix, as Rob suggests, or re-think the UI so you have fewer controls on it :-)
You could build the entire interface programmatically, with a few lines of code in a loop:
const int numRows = 11;
const int rowWidth = 400;
const int rowHeight = 20;
const int itemSpacing = 5;
const int nameFieldWidth = 120;
const int smallFieldWidth = 30;
NSMutableArray * rowList = [[NSMutableArray alloc] initWithCapacity:numRows];
int rowIndex;
NSRect rowFrame = [controlView bounds];
rowFrame.origin.y = rowFrame.size.height - rowHeight;
rowFrame.size.height = rowHeight;
NSRect itemRect
for (rowIndex = 0; rowIndex < 11; rowIndex++)
{
// create a new controller for the current row
MyRowController * rowController = [[MyRowController alloc] init];
[rowList addObject:rowController];
[rowController release];
// create and link the checkbox
itemRect = rowFrame;
itemRect.size.width = 20;
NSButton * checkBox = [[NSButton alloc] initWithFrame:itemRect];
[controlView addSubview:checkBox];
[rowController setCheckBox:checkBox];
[checkBox release];
// create and link the name field
itemRect.origin.x += itemRect.size.width + itemSpacing;
itemRect.size.width = nameFieldWidth;
NSTextField * nameField = [[NSTextField alloc] initWithFrame:itemRect];
[controlView addSubview:nameField];
[rowController setNameField:nameField];
[nameField release];
// create and link the smaller fields
itemRect.origin.x += itemRect.size.width + itemSpacing;
itemRect.size.width = smallFieldWidth;
NSTextField * smallField_1 = [[NSTextField alloc] initWithFrame:itemRect];
[controlView addSubview:smallField_1];
[rowController setSmallField_1:smallField_1];
[smallField_1 release];
//.. continue for each item in a row ..
// increment the main rectangle for the next loop
rowFrame.origin.y -= (rowFrame.size.height + itemSpacing);
}