Change the string of UILabel.text - ios

aClass.aString = #"A";
self.aLabel.text = aClass.aString;
If I change aClass.aString to "B", also want self.aLabel.text change to "B" automatically.
How it works?
Is there a strong link exist that I can built between aClass.aString and self.aLabel.text?
I knew that there some way to do this in objective-c, delegate and KVO, but it get a lot stuff to do, it's there some easy way?

You'll have to manage that yourself. If you look at the definition of the UILabel text property (link) you'll see that it copies the string contents, precisely for this very reason:
#property(nonatomic, copy) NSString *text
You could write a custom setter method for the aString property:
- (void)setAString:(NSString *)aString
{
_aString = aString;
self.aLabel.text = aString;
}
(If you are following the current trend of letting clang generate the backing instance variables and accessor methods for you, then you will probably need to explicitly declare this particular one, i.e. using an instance variable called _aString).

One more way other than overriding the setter method,
- (void)setAString:(NSString *)aString
is using KVO to observe change in the string value.
[self addObserver: self
forKeyPath: #"aString"
options: NSKeyValueObservingOptionNew
context: NULL];
Implement this method. It will get called whenever the string value changes.
-(void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
//Set the label's text with new value.
}

You may want to use the mechanism better explained here:
Is there any data binding mechanism available for iOS?
The gist of it is there's no nice way to do it; there are, however, messy ways to do it using NSNotificationCenter and listening for specific changes.
UI bindings themselves don't exist on iOS (it was my biggest shock when converting from Mac OS X to iOS).

Related

What is forward invocation? What it is supposed to do? Why it is ever used? [duplicate]

This question already has answers here:
NSInvocation for Dummies?
(4 answers)
Closed 7 years ago.
I came across this code in a project, where it is using NSInvocation. I want to know what it is supposed to do, and why would we ever need that. Simple explanations would be appreciated. I am posting the code.
// Public interface
#interface CCDelegateSplitter : NSObject
- (void) addDelegate: (id) delegate;
- (void) addDelegates: (NSArray*) delegates;
#end
// Private interface
#interface CCDelegateSplitter ()
#property(strong) NSMutableSet *delegates;
#end
#implementation CCDelegateSplitter
- (id) init
{
self = [super init];
_delegates = [NSMutableSet set];
return self;
}
- (void) addDelegate: (id) delegate
{
[_delegates addObject:delegate];
}
- (void) addDelegates: (NSArray*) delegates
{
[_delegates addObjectsFromArray:delegates];
}
- (void) forwardInvocation: (NSInvocation*) invocation
{
for (id delegate in _delegates) {
[invocation invokeWithTarget:delegate];
}
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
NSMethodSignature *our = [super methodSignatureForSelector:selector];
NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
return our ? our : delegated;
}
- (BOOL) respondsToSelector: (SEL) selector
{
return [[_delegates anyObject] respondsToSelector:selector];
}
#end
I'll assume you know what an NSInvocation is (if not, it's a data structure that holds all the information needed to make a method call; think "blocks" from long before blocks were added to the language).
forwardInvocation: is one of the methods that the runtime will call if it cannot find an implementation for a method. So if you pass a -doSomething message to an object [object doSomething], it will first check whether it has a doSomething method. Then it will check its superclasses. It'll try dynamic method resolution (resolveInstanceMethod for instance). It'll look for a forwarding target (forwardingTargetForSelector:), and it'll finally, if everything else fails, it'll create an invocation (using methodSignatureForSelector: and punt to forwardInvocation:. By default, forwardInvocation: just calls doesNotRecognizeSelector: which crashes you on iOS (or terminates the current thread on OS X). But you can override it to do something else (like they have here).
methodSignatureForSelector: is necessary so that the runtime system can create an invocation out of a message. This one either returns a method signature from this object or its superclasses, or it asks one of its targets for the appropriate method signature. A selector by itself isn't enough to figure out exactly how to call a method. The system needs to ask an object how the method actually works (what types it takes and what type it returns).
The code you've posted is a multi-delegate trampoline. It will accept any selector that its targets respond to (technically it'll pick a random target and see if it responds), and it'll forward that message to all of its targets.
For a similar trampoline with some comments about usage, you may want to look at RNObserverManager.
Take a look at the link in bfitch's comment. That covers what NSInvocation is, and hints at why you would use it, but doesn't cover the why in much detail.
NSInvocation lets you do fairly advanced things like creating a proxy object that actually forwards messages to another object. With NSInvocation it's possible to take ANY message at runtime and forward it to another object.
Another example is the performSelector family of methods. There's performSelector:, performSelector:withObject:, and performSelector:withObject:withObject:. (plus variants like performSelector:withObject:afterDelay:, performSelector:onThread:, etc.) Those methods take 0, 1, or or 2 objects as parameters. If you need to invoke a method on another object that takes scalar parameters, or anything other than 0, 1 or 2 objects, you're out of luck. However you can send messages with ANY type of parameters using NSInvocation.
Note that when blocks were added to Objective-C the need for tricks like performSelector and NSInvocation become less. Blocks can reference variables from their enclosing scope, which makes them more flexible.

Can a UIViewController written in Swift act as an Observer for an Object written in ObjC

I am trying to observe value changes of an NSMutableArray in an object written in ObjectiveC. The observer however is an UIViewController written in Swift.
I'm not able to get the KVO notifications. These are my changes:
#interface Record : BaseObject
#property (nonatomic) NSMutableArray *commentsArray;
-(NSUInteger) countOfCommentsArray {
return _commentsArray.count;
}
-(id) objectInCommentsArrayAtIndex:(NSUInteger)index {
return [_commentsArray objectAtIndex:index];
}
-(void) insertObject:(Comment *)comment inCommentsAtIndex:(NSUInteger)index {
[_commentsArray insertObject:comment atIndex:index];
}
-(void) insertComments:(NSArray *)array atIndexes:(NSIndexSet*)indexes {
[_commentsArray insertObjects:array atIndexes:indexes];
}
-(void) removeCommentsAtIndexes:(NSIndexSet *)indexes {
[_commentsArray removeObjectsAtIndexes:indexes];
}
-(void) removeObjectFromCommentsAtIndex:(NSUInteger)index {
[_commentsArray removeObjectAtIndex:index];
}
-(void) replaceObjectInCommentsAtIndex:(NSUInteger)index withObject:(id)object {
[_commentsArray replaceObjectAtIndex:index withObject:object];
}
The BaseObject derives from NSObject.
In my Observer class which is a Viewcontroller, and written in Swift I have the following.
In one of setup methods.
myInspectionRecord?.addObserver(self, forKeyPath: "commentsArray", options: NSKeyValueObservingOptions.New, context: nil);
And I have the method overrriden.
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("Value Changed");
}
But somehow when I add a new object to the array using the API itself, the observeValue method doesnt seem to be triggered.
Any pointers on what I might be missing here?
Or doesn't KVO work between Swift and Objective C files?
Any help would be appreciated.
Thanks!
You've implemented the to-many key-value coding (!) methods according to Apple's KVC guide, but the method names are wrong.
Since your property is named commentsArray, all of the methods need to include that full name. For example, your insertObject:inCommentsAtIndex: should be named insertObject:inCommentsArrayAtIndex:.
According to this answer this should then give you the KVO calls for free, but you have to actually call them: the compiler seems to recognize that it needs to inject the required KVO calls for these methods.
The problem right now with your code is, neither of your methods alter the property and even if the compiler does inject KVO code, it would do so for the wrong key path (comments instead of commentsArray). There are two solutions: it seems using those KVC-methods give you KVO for free but I've never used this magic, so I don't know from experience whether it will work. It should, though.
There is a "brute-force" way that you could use if you want to modify the array in a completely different method: call the KVO methods willChangeValueForKey: and didChangeValueForKey: or even better, willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: yourself, like this:
[self willChangeValueForKey:NSStringFromSelector(#selector(commentsArray))];
// Modify commentsArray
[self didChangeValueForKey:NSStringFromSelector(#selector(commentsArray))];
// or better, for an insertion:
NSIndexSet *set = [NSIndexSet indexSetWithIndex:someIndex];
NSString *key = NSStringFromSelector(#selector(commentsArray));
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:set forKey:key];
[_commentsArray insertObject:someObject atIndex:someIndex];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:set forKey:key];
(I like to use NSStringFromSelector(#selector(commentsArray)) instead of #"commentsArray" since the compiler then knows it's a selector and refactoring will then warn you when you rename the property, for example.)

Is it ok not to invoke [super init] in a custom init method?

I have a MKPolyline subblass which I want to implement NSCoding, i.e.
#interface RSRoutePolyline : MKPolyline <NSCoding>
I asked a question on the best way to encode the c-array and got an excellent answer. However, there is no init method defined on MKPolyline, i.e. there is no other way to give it data other than its class method polylineWithPoints:points.
Is this code where my comment is ok?
- (void)encodeWithCoder:(NSCoder *)aCoder
{
MKMapPoint *points = self.points;
NSUInteger pointCount = self.pointCount;
NSData *pointData = [NSData dataWithBytes:points length:pointCount * sizeof(MKMapPoint)];
[aCoder encodeObject:pointData forKey:#"points"];
[aCoder encodeInteger:pointCount forKey:#"pointCount"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSData* pointData = [aDecoder decodeObjectForKey:#"points"];
NSUInteger pointCount = [aDecoder decodeIntegerForKey:#"pointCount"];
// Edit here from #ughoavgfhw's comment
MKMapPoint* points = (MKMapPoint*)[pointData bytes];
// Is this line ok?
self = (RSRoutePolyline*)[MKPolyline polylineWithPoints:points count:pointCount];
return self;
}
You should call an init method on any subclass of NSObject. Since MKPolyline is an NSObject, you should init it.
But MKPolyline has no methods and no init. This is Objective C's was of telling you that you can't subclass it.
Instead, as WDUK suggested, define your own class. It keeps track of your list point points, and manages NSCoding to save and restore them as needed.
#interface RSPolyline: NSObject<NSCoding>
- (id) initWithPoints: (NSArray*) points;
- (id) initWithCoder:(NSCoder *)aDecoder;
- (void) encodeWithCoder:(NSCoder *)aCoder;
- (MKPolyline*) polyLine;
#end
Your class can generate a polyline on request, perhaps caching the result if performance is an issue.
As a rule, don't reach for inheritance first. When you want to extend and improve a class, think first of composition.
It's dirty not to call [super init], and it doesn't bode well with my idea of good programming. Without calling super yourself, it isn't a true subclass; just a bastardization of composition that relies on a side effect of calling a convenience constructor. Saying this, I believe your method described will work OK, but it goes against the grain of good Objective-C programming and its conventions.
What I would suggest is to use MKPolyLine as an MKPolyLine instance, and use a category to add the extra bells and whistles you need. As for adding extra instance variables and such, you can use associated objects. An introduction to this concept can be found here, and this SO question addresses the use of them with categories: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
While it is generally allowed to create and return a different object in an init method, there are three problems with that line (explained below). Instead of this, I would suggest overriding the points and pointCount properties so that you can return values stored in an instance variable, and call the super implementation there if the instance variable is empty. Then, your initializer just sets these instance variables so that they will be used.
- (MKMapPoint *)points {
if(myPointsIvar == NULL) return [super points];
else return myPointsIvar;
}
// similarly for pointCount
The first problem is that you are creating a new object, but not releasing the old one, which means you are leaking it. You should store the result in a different variable, then release self, then return the result (you don't need to store it in self).
Second, polylineWithPoints:count: returns an autoreleased object, but initWithCoder: should return a retained one. Unless there is another retain on it, it could be deallocated while you are still using it.
If these were the only problems, you could solve both like this:
MKPolyline *result = [MKPolyline polylineWithPoints:points count:pointCount];
[self release];
return [result retain];
However, there is a third problem which cannot be solved so easily. polylineWithPoints:count: does not return a RSRoutePolyline object, and the object it returns may not be compatible with your subclass's methods (e.g. it probably won't support NSCoding). There really isn't a way to fix this, so you can't use polylineWithPoints:count:.

ios access potentially undefined object

I am accessing a dispatched notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUnpresent:) name:UNPRESENT_VIEW object:nil];
...
-(void)handleUnpresent:(NSNotification *)note;
{
NSLog(#"%#", note.object.footer);
//property 'footer' not found on object of type 'id'
}
Some of the incoming note.object objects have a "footer" and some don't. However, I don't want to go through to trouble of making a class that only has a property called footer just to make this work. I even tried ((NSObject *)note.object).footer) which works in some languages, but apparently not obj-c. What can I do?
Checking the isKindOfClass is certainly the more robust option. However, if you have multiple unrelated classes that return the property you need, there is another way: respondsToSelector. Just ask if the object has a footer method, and you can safely call it.
-(void)handleUnpresent:(NSNotification *)note;
{
id noteObject = [note object];
if ([note respondsToSelector:#selector(footer)])
{
NSLog(#"Footer = %#", [noteObject footer]);
}
}
That respondsToSelector method is powerful and handy in the right places, but don't go wild with it. Also, it can't tell you anything about the return type, so the footer you get may not be of the class you were expecting.
The syntax for noteObject.footer and [noteObject footer] are easy to treat as equivalent. However, when the class of noteObject is unknown, the compiler will accept the latter but not the former. If noteObject has a defined class that doesn't usually respond to footer, it will give a warning, but still compile and run. In these cases, it is your responsibility to guarantee that the method will indeed exist when needed, and therefore that the method call won't crash at run time.
If the object passed in the notification may be one of a number of classes and you don't want to cast the object to a specific class you can use performSelector: to call the footer method on the object. If you wrap this call with a respondsToSelector: you'll avoid an exception if the object turns out not to have a footer method.
-(void)handleUnpresent:(NSNotification *)note;
{
if ([[note object] respondsToSelector:#selector(footer)]) {
NSString *footer = [[note object] performSelector:#selector(footer)];
NSLog(#"%#", footer);
}
}
Using performSelector will stop the compiler complaining that the method "'footer' not found on object of type 'id'."
NSObject doesn't have any property named footer, which is why the compiler is complaining. Casting an id back to an NSObject doesn't help. If you know the object is always going to be some custom object you've created, you can cast back to that and then call footer and the compiler won't complain. It's best to actually check tho. See the example below (for the example, I named the class that has the footer property ViewWithFooter, so rename appropriately):
- (void)handleUnpresent:(NSNotification*)note
{
ViewWithFooter view = (ViewWithFooter*)[note object];
NSParameterAssert([view isKindOfClass:[ViewWithFooter class]]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}
If you have a bunch of unrelated classes (i.e., not in the same class hierarchy) that all present a footer property, you'd be best served creating a protocol with the required footer property and casting the object to the protocol in the code example above and asserting it responds to the -footer selector.
Here's an example using the protocol:
#protocol ViewWithFooter <NSObject>
- (UIView*)footer; // this could also be a readonly property, or whatever
#end
- (void)handleUnpresent:(NSNotification*)note
{
id<ViewWithFooter> view = (id<ViewWithFooter>)[note object];
NSParameterAssert([view respondsToSelector:#selector(footer)]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}

KVO (IOS 5) on simple types (non NSObject)

I'm having problems to make the IOS (objective-c) KVO work for a key of type int.
My class declares a property sampleValue of type int. As int doesn't automatically implement the KVO functionality I've overrided the method automaticallyNotifiesObserversforKey as this:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"sampleValue"]) {
automatic = NO;
} else {
automatic=[super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
The method is called just as I would expect is to be. I also have implemented a setter method for the sampleValue property like this:
- (void) setSampleValue:(int)newSampleValue
{
[self willChangeValueForKey:#"sampleValue"];
sampleValue = newSampleValue;
[self didChangeValueForKey:#"sampleValue"];
}
Setting up the observer in the observer class is done like this (dc is the instance of the observed object):
[dc addObserver:self forKeyPath:#"sampleValue" options:NSKeyValueObservingOptionNew context:NULL];
However, when the sampleValue is updated, no notification is sent to my observer object. Updating another property of type NSDate works absolutely fine.
Can anyone help me figure out what I'm doing wrong or what I should do to make this work.
Best regards
Tomo
Maybe I'm missing something in your question, but you can observe properties of type int just as easily as other types without doing anything special.
Try removing your +automaticallyNotifiesObserversForKey: override and your -setSampleValue: setter, and just synthesize the accessors for sampleValue:
#synthesize sampleValue;
int is the type of the value that corresponds to key #"sampleValue", but it's not the thing being observed. The object being observed is dc, and it'll take care of sending the proper notification when the sampleValue property is changed.

Resources