Calling only parts of a method - objective-c

If I have a method called
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha andEnabled:(BOOL)enabled {
button.alpha = alpha;
button.enabled = enabled;
}
Is there anyway I can call only [self changeButton:buttonName andAlpha:0.5]; and miss out andEnabled(BOOL) enabled so that it will stay the same value.

No, only if you declare other methods.
like
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha {
[self changeButton:button andAlpha:alpha andEnabled:button.enabled];
}
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha andEnabled:(BOOL)enabled {
button.alpha = alpha;
button.enabled = enabled;
}
But remember, that this approach is not always good. For example enabled property could be backed up by some custom setter which will be called even if you don't want to change that value.

You can't do this, you must declare another method.
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha;

I am sure you are asking about default parameterized function of c++.
But Objective-C doesn't support this.
You can create 2 methods though:
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha {
button.alpha = alpha;
}
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha andEnabled:(BOOL)enabled {
button.alpha = alpha;
button.enabled = enabled;
}
For C : there's nothing special added to the C subset by using ObjC. Anything that cannot be done in pure C can't be done by compiling in ObjC either. That means, you can't have default parameters, nor overload a function. Create 2 functions instead.
One alternate way(bit lengthy, as someone will use rarely) is to have a flag and check the flag for YES/NO.
-(void) changeButton:(UIButton *)button andAlpha:(float)alpha andEnabled:(BOOL)enabled withFlag:(BOOL)flag{
button.alpha = alpha;
if(flag){
button.enabled = enabled;
}
}

Related

Objective C - Make Method Call Itself Only Once

If I have this method:
-(void) methodName
{
action1;
action2;
[self methodName];
}
I want the [self methodName] call to be done only once, therefore the method to be called only twice consecutively. Can this be done? Not sure where in the docs I should be looking.
Whenever method 'methodName' is called, then when action1 and action2 are done, it should call itself again, but only once. The way it is done in the sample code I have written is going on forever (I am guessing).
If you mean to say only once during the entire lifetime of the application, you can use dispatch_once, like this:
-(void)methodName
{
action1;
action2;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self methodName];
});
}
If, however, you meant for the method to execute action1 and action2 twice per invocation, you have two options:
1) Wrap that functionality in another method:
- (void)executeMethod {
[self methodName];
[self methodName];
}
2) Even simpler, wrap it in a loop:
- (void)methodName {
for(int i = 0; i < 2; ++i) {
action1();
action2();
}
//...
}
You can use static variable:
-(void) methodName
{
static BOOL called = NO;
if (called == NO)
{
called = YES;
[self methodName];
}
else
{
called = NO;
}
}
The simplest thing to do is split that in two methods:
-(void) subMethodName
{
action1;
action2;
}
-(void) methodName
{
[self subMethodName];
[self subMethodName];
}
Or to use a loop of some form or other.
(What you have in your original code is infinite recursion - not a good thing in general.)
You need to apply some logic on it. This can be using Bool Variable Or simply a count.
Take
int Count=0; //as a global variable.
Now just modify your method as follows
-(void) methodName
{
Count++;
action1;
action2;
if(Count==1)
[self methodName];
}

How to name a block of code and call it in a different method?

I use Grand Central Dispatch methods to do some executions of my app in a queue. I decide the frames for buttons in a calculation on that queue. I want my app to re-draw its scren and calculate new frames after rotation. Here is some pseudo code explanation from what i do:
CGFloat a=123, b=24;
dispatch_async(drawingQue, ^{
//needed loops to get the total button count-how many ones will be drawn et..
for(int x=0;x<someCount<x++){
for(int y=0;y<anotherCount;y++){
//needed frame&name ect assingments
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];}}
};
Here what i want is, how can i give this block a name and re-use it in the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
}
delegate method? For instance, if the rotation is landscape, i want to use a=234 instead of 123.. Any help please. Thanks in advance..
Declare an instance variable of block type and use Block_copy to keep the block:
#interface My {
void (^myBlock)(void);
}
#end
myBlock = Block_copy(^{
...block code...
});
// later call it
myBlock();
// don't forget to release it in dealloc
It is important to copy the block before storing it outside of the scope of its literal (^{...}), because the original block is stored on stack and will die when the scope exits.
Just make a #property that's a block, store it, and use it again later:
typedef void (^MyBlock)(CGFloat, CGFloat);
...
#property(readwrite, copy) MyBlock buttonFramesBlock;
...
#synthesize buttonFramesBlock;
...
self.buttonFramesBlock = ^(CGFloat a, CGFloat b){
//needed loops to get the total button count-how many ones will be drawn et..
for(int x=0;x<someCount<x++){
for(int y=0;y<anotherCount;y++){
//needed frame&name ect assingments
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];}}
};
...
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
dispatch_async(drawingQue, ^{
self.buttonFramesBlock(234,someOtherInt);
});
}
First, never change UI outside of the main thread. So your should modify your code into something like:
dispatch_async(drawingQue, ^{
// ...
dispatch_async(dispatch_get_main_queue(), ^{
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];
});
});
Second, never change UI inside the method shouldAutorotateToInterfaceOrientation. All you have to do inside that method is return whether the view should rotate or not. For instance, in some cases where you have a view controller hierarchy, the view might not get rotated even if you return YES in shouldAutorotateToInterfaceOrientation.
So, you should call your code inside the method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
This can be achieved in many ways. The simplest (and the one I recommend) is to use a standard Objective-C method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { // landscape
[self rotateButtonWithA:234 b:24];
} else { // portrait
[self rotateButtonWithA:123 b:24];
}
}
- (void)rotateButtonWithA:(CGFloat)a b:(CGFloat)b
{
dispatch_async(drawingQue, ^{
// ...
dispatch_async(dispatch_get_main_queue(), ^{
button.frame= CGRectMake(x+y, x-y, a, b);
[button setTitle:#"abc"];
});
});
}
You don't really need to call the block itself from multiple places. But if you still want to do that, there are many answers here that show you how to do that.

Pass Button object to method

I am trying to write a generic method that would allow me to pass the following: "x", "y" "object" and then have it move. Currently I have this:
-(void) changeObjectLocations: (integer_t) xSpot: (integer_t) ySpot: (id) sender {
if (![sender isKindOfClass:[UIButton class]])
{
UIButton *myObject = (UIButton *)sender;
[UIView animateWithDuration:1.5
animations:^{
CGRect newFrame = myObject.frame;
newFrame.origin.x = xSpot;
newFrame.origin.y = ySpot;
myObject.frame = newFrame;
}];
}
else if (![sender isKindOfClass:[UILabel class]])
{
UILabel *myObject = (UILabel *)sender;
[UIView animateWithDuration:1.5 animations:^{
CGRect newFrame = myObject.frame;
newFrame.origin.x = xSpot;
newFrame.origin.y = ySpot;
myObject.frame = newFrame;
}];
}
}
I then want to call it like so:
-(void) orientationBlockLandscape {
[self changeObjectLocations: 456 :282 : btn1] ;
[self changeObjectLocations: 391 :227 : lblTitle] ;
}
Although it is working, on compile I get the following warning:
SecondViewController.m:33: warning: 'SecondViewController' may not respond to '-changeObjectLocations:::'
Is there a better way I can/should be passing the object? Thanks in advance for any and all help.
Geo...
Based on the warning outputted, it sounds like you didn't define changeObjectLocations in the header of your SecondViewController -- or it's not the same signature as what you've implemented.
The compiler looks for selectors which are the method definitions with the variables and return removed.
So a method like:
-(void) setObject:(id) anObject forKey:(NSString *) keyname;
... would have a selector of:
setObject:forKey:
Therefore, your method of:
-(void) changeObjectLocations: (integer_t) xSpot: (integer_t) ySpot: (id) sender
... will have a selector of:
changeObjectLocations:xSpot:ySpot:
Note that the parameter names are part of the selector so:
changeObjectLocations:xSpot:ySpot:
and
changeObjectLocations:::
.. are two entirely separate selectors which represent two entirely separate methods.
Although it is legal in the language to use parameters without names e.g. ":::" it is very, very poor practice largely because it is to easy to get a naming collision. Being explicit not only makes the code easier to read and maintain but makes it easier for the complier and runtime to function.

Override UILabel's setText method?

I'd like to override UILabel's setText method but I'm not sure that:
A) it's possible, and
B) if maybe there's a better way to achieve what I'm trying to accomplish.
I have a subclass of UIView that has a UILabel property as one of its sub-views. I'd like to know when the UILabel's "text" property changes so I can adjust the size of the rounded rect behind it. If I owned UILabel I'd just override setText...but UILabel is Apple's and its implementation is hidden.
So, how should I be going about this?
Subclasses of UILabel can override the setText method quite easily. I'm not really sure why this hasn't yet been included as a legitimate answer on this 4 year old question.
- (void) setText:(NSString *)text
{
[super setText:text];
[self sizeToFit];
}
You can use Key-Value Observing to track changes to the UILabel.text property. The approach involves three steps:
1) Registering to observe the property, when you load the view
[label addObserver:inspector
forKeyPath:#"text"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
2) Receiving a notification about any changes:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"text"]) {
// put your logic here
}
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
3) De-registering the observation whenever you aren't interested any more:
[label removeObserver:inspector forKeyPath:#"text"];
Matt answer is good for Objective-C, but doesn't work in Swift (normal, it didn't existed when he answered), the accepted answer from notnoop does work in swift, even though it is more complicated, just to give another idea in swift you can use the didSet:
class MyLabel: UILabel {
override var text: String? {
didSet {
if let text = text {
println("the new text is: \(text)")
} else {
println("the text has been set to nil")
}
}
}
Based on Drix answer, I think this is a more correct approach (using set instead of didSet):
class UnreadCountLabel: UILabel {
override var text: String? {
set {
if let newValue = newValue where !newValue.isEmpty {
super.text = " \(newValue) "
self.hidden = false
} else {
super.text = newValue
self.hidden = true
}
}
get {
return super.text
}
}
}
Are you just using a rounded rectangle as the background for the Label? If that is the case, you can look into using UIIMage stretchableImageWithLeftCapWidth:topCapHeight. This will take an image you've created that has a left and top repeating section with a width you specify and automatically stretch it to your width.
If not, Key-Value observing is the way to go. Just to cover another option--this is like "playing with fire," as Apple programmer Evan Doll said in one of his Stanford lectures--you can use method swizzling to exchange one method implementation for another.
void method_exchangeImplementations(Method m1, Method m2);
In this case, you want to tweak the implementation of setText, but you also want to call the original setText in UILabel. So you could exchange setText with setTextAndUpdateSize, and inside setTextAndUpdateSize do what setText does originally plus add on a little more. If you are confused or think this is a bad idea, it probably is. You can get a Method object to pass into method_exchangeImplementations by calling class_getInstanceMethod([NSSTring class], #selector (methodName).
Once your method swizzle has been called once, inside your new method you can then call the old implementation of setText from within the new one by using, yes, setTextAndUpdateSize. It's confusing and not recommended, but it works. A good example can be found in the developer sample code.
I pulled of Method Swizzling in Swift 2.0. Changing the font of the entire application by swapping the implementation of setText method of the label.
Copy the code in app delegate and use the customSetText to make application level changes
// MARK: - Method Swizzling
extension UILabel {
public override class func initialize() {
struct Static {
static var token: dispatch_once_t = 0
}
// make sure this isn't a subclass
if self !== UILabel.self {
return
}
dispatch_once(&Static.token) {
let originalSelector = Selector("setText:")
let swizzledSelector = Selector("customSetText:")
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
// MARK: - Custom set text method for UI Label
func customSetText(text: String) {
self.customSetText(text)
//set custom font to all the labels maintaining the size UILabel
self.font = UIFont(name: "Lato-LightItalic", size: self.font.pointSize)
}
}

Arrow keys with NSTableView

Is it possible to navigate an NSTableView's editable cell around the NSTableView using arrow keys and enter/tab? For example, I want to make it feel more like a spreadsheet.
The users of this application are expected to edit quite a lot of cells (but not all of them), and I think it would be easier to do so if they didn't have to double-click on each cell.
In Sequel Pro we used a different (and in my eyes simpler) method: We implemented control:textView:doCommandBySelector: in the delegate of the TableView. This method is hard to find -- it can be found in the NSControlTextEditingDelegate Protocol Reference. (Remember that NSTableView is a subclass of NSControl)
Long story short, here's what we came up with (we didn't override left/right arrow keys, as those are used to navigate within the cell. We use Tab to go left/right)
Please note that this is just a snippet from the Sequel Pro source code, and does not work as is
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
NSUInteger row, column;
row = [tableView editedRow];
column = [tableView editedColumn];
// Trap down arrow key
if ( [textView methodForSelector:command] == [textView methodForSelector:#selector(moveDown:)] )
{
NSUInteger newRow = row+1;
if (newRow>=numRows) return TRUE; //check if we're already at the end of the list
if (column>= numColumns) return TRUE; //the column count could change
[tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[tableContentView editColumn:column row:newRow withEvent:nil select:YES];
return TRUE;
}
// Trap up arrow key
else if ( [textView methodForSelector:command] == [textView methodForSelector:#selector(moveUp:)] )
{
if (row==0) return TRUE; //already at the beginning of the list
NSUInteger newRow = row-1;
if (newRow>=numRows) return TRUE;
if (column>= numColumns) return TRUE;
[tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[tableContentView editColumn:column row:newRow withEvent:nil select:YES];
return TRUE;
}
Well it isn't easy but I managed to do it without having to use RRSpreadSheet or even another control. Here's what you have to do:
Create a subclass of NSTextView, this will be the field editor. For this example the name MyFieldEditorClass will be used and myFieldEditor will refer to an instance of this class.
Add a method to MyFieldEditorClass called "- (void) setLastKnownColumn:(unsigned)aCol andRow:(unsigned) aRow" or something similar, and have it save both the input parameter values somewhere.
Add another method called "setTableView:" and have it save the NSTableView object somewhere, or unless there is another way to get the NSTableView object from the field editor, use that.
Add another method called - (void) keyDown:(NSEvent *) event. This is actually overriding the NSResponder's keyDown:. The source code should be (be aware that StackOverflow's MarkDown is changing < and > to < and >):
- (void) keyDown:(NSEvent *) event
{
unsigned newRow = row, newCol = column;
switch ([event keyCode])
{
case 126: // Up
if (row)
newRow = row - 1;
break;
case 125: // Down
if (row < [theTable numberOfRows] - 1)
newRow = row + 1;
break;
case 123: // Left
if (column > 1)
newCol = column - 1;
break;
case 124: // Right
if (column < [theTable numberOfColumns] - 1)
newCol = column + 1;
break;
default:
[super keyDown:event];
return;
}
[theTable selectRow:newRow byExtendingSelection:NO];
[theTable editColumn:newCol row:newRow withEvent:nil select:YES];
row = newRow;
column = newCol;
}
Give the NSTableView in your nib a delegate, and in the delegate add the method:
- (BOOL) tableView:(NSTableView *)aTableView shouldEditColumn:(NSTableColumn *) aCol row:aRow
{
if ([aTableView isEqual:TheTableViewYouWantToChangeBehaviour])
[myFieldEditor setLastKnownColumn:[[aTableView tableColumns] indexOfObject:aCol] andRow:aRow];
return YES;
}
Finally, give the Table View's main window a delegate and add the method:
- (id) windowWillReturnFieldEditor:(NSWindow *) aWindow toObject:(id) anObject
{
if ([anObject isEqual:TheTableViewYouWantToChangeBehaviour])
{
if (!myFieldEditor)
{
myFieldEditor = [[MyFieldEditorClass alloc] init];
[myFieldEditor setTableView:anObject];
}
return myFieldEditor;
}
else
{
return nil;
}
}
Run the program and give it a go!
Rather than forcing NSTableView to do something it wasn't designed for, you may want to look at using something designed for this purpose. I've got an open source spreadsheet control which may do what you need, or you may at least be able to extend it to do what you need: MBTableGrid
I wanted to reply to the answers here but the reply button seems to be missing so I'm forced to proved an answer when I really just want to ask a question about the replies.
Anyway, I've seen a few answers for overriding the -keyDown event of the table view that say to subclass the TableView but according to every Objective-C book I've read so far, and several Apple training videos, you should very rarely if ever subclass one of the core classes. In fact every single one of them makes the point that C programmers have a fascination with subclassing and that's not how Objective-C works; that Objective-C is all about helpers and delegates not subclassing.
So, should I just ignore any of the responses that say to subclass as this seems to be in direct contradiction to the precepts of Objective-C?
--- Edit ---
I found something that worked without subclassing the NSTableView. While I do move the inheritance up one notch on the chain from NSObject to NSResponder I'm not totally subclassing the NSTableView. I'm just adding the ability to override the keyDown event.
I made the class I was using as a delegate inherit from NSResponder instead of NSObject and set the nextResponder to that class in awakeFromNib. I was then able to trap key presses using the keydown event. I of course connected the IBOutlet and set the delegate in Interface Builder.
Here's my code with the minimum needed to show the trapping of the key:
Header file
// AppController.h
#import <Cocoa/Cocoa.h>
#interface AppController : NSResponder {
IBOutlet NSTableView *toDoListView;
NSMutableArray *toDoArray;
}
-(int)numberOfRowsInTableView:(NSTableView *)aTableView;
-(id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex;
#end
Here's the m file.
// AppController.m
#import "AppController.h"
#implementation AppController
-(id)init
{
[super init];
toDoArray = [[NSMutableArray alloc] init];
return self;
}
-(void)dealloc
{
[toDoArray release];
toDoArray = nil;
[super dealloc];
}
-(void)awakeFromNib
{
[toDoListView setNextResponder:self];
}
-(int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [toDoArray count];
}
-(id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
NSString *value = [toDoArray objectAtIndex:rowIndex];
return value;
}
- (void)keyDown:(NSEvent *)theEvent
{
//NSLog(#"key pressed: %#", theEvent);
if (theEvent.keyCode == 51 || theEvent.keyCode == 117)
{
[toDoArray removeObjectAtIndex:[toDoListView selectedRow]];
[toDoListView reloadData];
}
}
#end