IOS 7 - Update an UILabel with value computed in background using delegation - xcode

I am having a problem trying to update UILabel.text using a value calculate in background using delegation (which seems to be slow).
#interface BTAViewController : UIViewController <ExchangeFetcherManagerDelegate>
#property (weak, nonatomic) IBOutlet UILabel *bidPriceLabel;
#property (strong, nonatomic) FetcherManager *fetcherManager;
#end
my BTAViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.fetcherManager = [[FetcherManager alloc]init];
double calculateValue = [self.fetcherManager fetchPricesFor];
self.bidPriceLabel.contentMode = UIViewContentModeRedraw;
[self.bidPriceLabel setNeedsDisplay];
self.bidPriceLabel.text = [[NSString alloc] initWithFormat:#"%f", calculateValue];
}];
The label has been correctly initialised in a method not shown here and it shows the correct default value at startup. Once I execute this code, the label will get the value 0.00000000, because my calculateValue has not been calculate yet.
How can I wait for this value to be calculated and then display it has text of the label ?
I can't post images because I don't have enough reputation...
Thanks

Are you sure the value calculated is not purely 0 or null?
What I would do is to add a delegate callback function in BTAViewController.m from FetchManager
Something like this:
BTAViewController.h
//add delegate callback function under #interface
-(void)valueReturned:(float)someValue;
BTAViewController.m
-(void)valueReturned:(float)someValue{
//updates value when the delegate returns the value
self.bidPriceLabel.text = [[NSString alloc] initWithFormat:#"%f", calculateValue];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.fetcherManager = [[FetcherManager alloc]init];
self.fetchManager.delegate = (id)self;
[self.fetchManager getPricesFor];
// double calculateValue = [self.fetcherManager fetchPricesFor];
self.bidPriceLabel.contentMode = UIViewContentModeRedraw;
[self.bidPriceLabel setNeedsDisplay];
[self.view addSubview:self.bidPriceLabel];
}];
In FetchManager.h and .m
FetcherManager.h
#property (retain,nonatomic)id<ExchangeFetcherManagerDeelgate>delegate;
-(void)getPricesFor;
FetcherManager.m
#synthesize delegate;
-(void)getPricesFor{
float price;
//calculate price
[self.delegate valueReturned:price];
}

Add the label to the view, set it to be hidden (bidPriceLabel.hidden = YES). Then, create and add a method for the delegate (BTAViewController) which when fired, checks the hidden property of the UILabel, and if it is TRUE (is hidden), set bidPriceLabel.hidden = NO which will then display the UILabel as you intended to.
Call this method in FetchManager once you are done retrieving the information.

Related

Why is NSViewController not binding representedObject?

In short: I bind an NSTextField to the File's Owner (the view controller) and Model Key Path of representedObject.firstName, but editing the text field does not change the firstName.
Here are more details. I have a simple program that does nothing but create an instance of Thing (a simple class with some properties), and ThingViewController. The controller has an associated .xib with a simple UI -- a couple text fields to bind to properties of the Thing.
#interface Thing : NSObject
#property (nonatomic, strong) NSString *firstName;
#property (nonatomic, strong) NSString *lastName;
#property (nonatomic) BOOL someBool;
#end
And in the app delegate...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *cv = self.window.contentView;
ThingViewController *vc = [[ThingViewController alloc]
initWithNibName:#"ThingViewController" bundle:nil];
theThing = [Thing new];
theThing.firstName = #"Rob";
vc.representedObject = theThing;
[cv addSubview:vc.view];
}
The ThingViewController.xib is simple:
And here is the binding for that first text field:
When I run, the text field does show "Rob", so it works in that direction, but then as I edit the text field, the firstName property of theThing does not change.
What am I doing wrong?
Edit: Here's a link to a zipped project file for the above code: https://drive.google.com/file/d/0B2NHW8y0ZrBwWjNzbGszaDQzQ1U/edit?usp=sharing
Nothing is strongly referencing your view controller (ThingViewController), other than the local variable in -applicationDidFinishLaunching:. Once that goes out of scope, the view controller is released and dealloc'ed. The view itself is still around, since it is a subview of your window's contentView.
Once your view controller is released/gone, the text field has no connection back to the Thing object so it is in effect calling [nil setValue:#"New first name" forKeyPath:#"representedObject.firstName"].
Add a strong reference to your view controller (e.g., an instance variable of your app delegate) and try it again.
#implementation AppDelegate {
Thing *theThing;
ThingViewController *vc;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *cv = self.window.contentView;
vc = [[ThingViewController alloc] initWithNibName:#"ThingViewController" bundle:nil];
theThing = [Thing new];
theThing.firstName = #"Rob";
vc.representedObject = theThing;
[cv addSubview:vc.view];
}

Custom UIControls with Storyboard

I'm having trouble figuring this one out. I have a custom UIControl class set up to hold a UIImage and UILabel, and my UITableViewCell class holds two of these UIControls (leftProduct, & rightProduct)
#interface FeaturedProductControl : UIControl
#property (strong, nonatomic) IBOutlet UIImageView *featuredProductPhoto;
#property (strong, nonatomic) IBOutlet UILabel *featuredProductDescription;
#property (strong, nonatomic) Product *featuredProduct;
- (id)initWithProduct:(Product *)product;
#end
#interface FeaturedTableCell : UITableViewCell
#property (strong, nonatomic) IBOutlet FeaturedProductControl *leftProduct;
#property (strong, nonatomic) IBOutlet FeaturedProductControl *rightProduct;
#end
The images and labels are being filled in using the init method during cellForRowAtIndexPath, and they're coming through just fine. I have a target action associated with the UIControls in the Storyboard, but the productClicked: method doesn't seem to be called. I've tried changing it out to add the target action programmatically, no luck.
However, if I add an alloc/init to the code, the productClicked: method triggers properly, but unfortunately the UILabel and UIPhoto now come up empty onscreen. Since the UIControls are designed in Storyboard, I think I'm not supposed to do the alloc calls myself, but the TableViewController doesn't seem to like that it's not being called. I've tried calling alloc within [[FeaturedTableCell alloc] init], but it had no effect.
Contents of cellForRowAtIndexPath:
cellIdentifier = #"Featured Row";
FeaturedTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[FeaturedTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
FeaturedRow *featuredRowData = [self.productIndexModel objectAtIndexPath:indexPath]; // This contains the necessary products to fill the Row
Product *leftProduct = [featuredRowData.skuList objectAtIndex:0];
cell.leftProduct = [[FeaturedProductControl alloc] initWithProduct:leftProduct]; // Actions trigger, but no data
// cell.leftProduct = [cell.leftProduct initWithProduct:leftProduct]; // Data is filled in, but no action
// [cell.leftProduct addTarget:self action:#selector(productClicked:) forControlEvents:UIControlEventTouchUpInside]; // the storyboard mapping works without this line of code
if (featuredRowData.skuList.count > 1)
{
Product *rightProduct = [featuredRowData.skuList objectAtIndex:1];
cell.rightProduct = [cell.rightProduct initWithProduct:rightProduct];
// cell.rightProduct = [[FeaturedProductControl alloc] initWithProduct:rightProduct]; // Yes, these two are reversed from the left side code above for testing
[cell.rightProduct addTarget:self action:#selector(productClicked:) forControlEvents:UIControlEventTouchUpInside];
cell.rightProduct.hidden = NO; // right side column is hidden in case the number of products is odd
}
else
{
cell.rightProduct.hidden = YES;
}
[cell setNeedsLayout];
return cell;
Any idea what I'm doing wrong? I'm trying to keep as much of the initialization and setup as possible inside the storyboard, so I'd prefer not to go back to writing the whole UIControl programmatically.
Thanks everyone!
Figured it out. In my initWithProduct method, I included
self = [super init];
I commented out this line and the Touch Up Inside events started firing normally.

Core Image help: Setting CIImage from UIImage via UIImagePicker (then filtering it)

Searched around on SO for an answer. Found some interesting stuff but I am still stuck.
In a view controller I have a UIImageView which I set an image taken from UIImagePicker when the view loads.
(I also set a CIImage at the same time)
I have two sliders (one for brightness, one for contrast). Upon moving the slider, a filter is applied to the CIImage, then the CIImage is rendered into the the UIImageView's UIImage.
Fist off, the UIImage taken from UIImagePicker does show up correctly when first selected.
I also have my slider ranges set correctly and have verified the proper float values are being passed to the delegate functions (via NSLog).
However, when I try playing with the sliders, my UIImageView turns white! Perhaps you folks can help. I'll post some code snippets:
First, my #interface:
#interface PrepViewController : UIViewController <UIImagePickerControllerDelegate , UINavigationControllerDelegate>
#property (weak, nonatomic) IBOutlet UIImageView *editingImage;
#property (weak, nonatomic) CIImage *editingCIImage;
#property (weak, nonatomic) CIContext *editingCIContext;
#property (weak, nonatomic) CIFilter *editingCIFilter;
#property (nonatomic) BOOL didAskForImage;
#property (weak, nonatomic) IBOutlet UISlider *brightnessSlider;
#property (weak, nonatomic) IBOutlet UISlider *contrastSlider;
- (void) doImageFilter:(NSString *)filter withValue:(float)value;
#end
My viewDidLoad, pretty simple just sets didAskForImage to NO
- (void)viewDidLoad
{
[super viewDidLoad];
self.didAskForImage = NO;
}
viewDidAppear is where I check if I've already asked for the image, then ask for it
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if(!self.didAskForImage){
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = NO;
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
sourceType = UIImagePickerControllerSourceTypeCamera;
}
picker.sourceType = sourceType;
[self presentModalViewController:picker animated:YES];
}
}
Here is the imagePickerController didFinishPickingImage delegate method
this is where I actually set the UIImageView's image property and initialize the CIImage, CIContext, and CIFilter objects.
I also set the didAskForImage boolean and the slider targets.
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage : (UIImage *)image
editingInfo:(NSDictionary *)editingInfo
{
self.didAskForImage = YES;
self.editingImage.image = image;
self.editingCIImage = [[CIImage alloc] initWithImage:image];
self.editingCIContext = [CIContext contextWithOptions:nil];
self.editingCIContext = [CIContext contextWithOptions:nil];
self.editingCIFilter = [CIFilter filterWithName:#"CIColorControls" keysAndValues:kCIInputImageKey, self.editingCIImage, nil];
[self.contrastSlider addTarget:self action:#selector(contrastMove:) forControlEvents:UIControlEventValueChanged];
[self.brightnessSlider addTarget:self action:#selector(brightnessMove:) forControlEvents:UIControlEventValueChanged];
[picker dismissModalViewControllerAnimated:YES];
}
and the cancel delegate
-(void)imagePickerControllerDidCancel:(UIImagePickerController *) picker
{
[picker dismissModalViewControllerAnimated:YES];
picker = nil;
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
}
Here are the sliders' delegate methods. Again, the NSLogs display the expected values.
- (void) contrastMove:(id)sender{
NSLog(#"%f", [(UISlider *)sender value]);
[self doImageFilter:#"inputContrast" withValue:[(UISlider *)sender value]];
}
- (void) brightnessMove:(id)sender{
NSLog(#"%f", [(UISlider *)sender value]);
[self doImageFilter:#"inputBrightness" withValue:[(UISlider *)sender value]];
}
And finally, here is the doImageFilter method I created, which actually does the filter and reassigns the UIImageView (this is where it turns white)
- (void) doImageFilter:(NSString *)filter withValue:(float)value{
[self.editingCIFilter setValue:[NSNumber numberWithFloat:value] forKey:filter];
CIImage *output = [self.editingCIFilter outputImage];
CGImageRef cgi = [self.editingCIContext createCGImage:output fromRect:[output extent]];
self.editingCIImage = output;
self.editingImage.image = [UIImage imageWithCGImage:cgi];
}
So I have a hunch that the CIImage isn't even being initialized properly in the first place; but I don't know why. Been at this for a couple hours now.
Thanks for all your help!
Cheers
Hahaha, oh wow.
So I figured it out, my CIContext and CIFilter need to be set to strong instead of weak.
Awesome.

ARC and Popovers and delegates

i am tearing my hair out, I have migrated my old project to arc and I'm getting this error popping up : * Terminating app due to uncaught exception 'NSGenericException', reason: '-[UIPopoverController dealloc] reached while popover is still visible.'
I have read through some threads and I'm confused, some say when using delegates use a weak reference but on the other hand when using popovers use a strong property reference, can someone give me an example of how best to use ARC and delegates with a popover that has a button inside that changes the background colour for example?
From what I've read I keep hearing use an instance variable in my view controller, here it is in my main view controller:
#property (nonatomic, strong) UIPopoverController *currentPopover;
and the is the method implementation in the main view controller file:
- (IBAction)ShowPopTextColor:(id)sender {
if (currentPopover == nil) {
TimerTextColor *timerTextColor = [[TimerTextColor alloc]init];
timerTextColor.delegate =self;
UIPopoverController *pop = [[UIPopoverController alloc]initWithContentViewController:timerTextColor];
[pop setDelegate:self];
[pop setPopoverContentSize:CGSizeMake(320, 240)];
[pop presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
//[pop release];
} else {
[currentPopover dismissPopoverAnimated:YES];
currentPopover = nil;
}
}
here is my popup content header:
#protocol colorChooserDelegate
-(void) colorSelected:(UIColor*)thecolor;
#end
#interface TimerTextColor : UIViewController{
id<colorChooserDelegate> delegate;
IBOutlet UIButton *colorView;
}
- (IBAction)buttonTapped:(id) sender;
#property (nonatomic,strong) id<colorChooserDelegate>delegate;
#property (nonatomic,strong) UIButton *colorView;
#end
What am i doing wrong?
Assign currentPopover.
Call
currentPopover = pop
after popover creation
you shouldn't create a local variable to store the popover controller.
Change this
UIPopoverController *pop = [[UIPopoverController alloc] initWithContentViewController:timerTextColor];
to
self.currentPopover = [[UIPopoverController alloc] initWithContentViewController:timerTextColor];

How to deal with a Toggle NSButton?

My application contains a PLAY/PAUSE button that is set to type Toggle in Interface Builder. I use it - as the name reveals - to play back my assets or to pause them.
Further, I am listening to the SPACE key to enable the same functionality via the keyboard shortcut. Therefore, I use keyDown: from NSResponderin my application. This is done in another subview. The button itself is not visible at this time.
I store the current state of playback in a Singleton.
How would you update the title/alternative title for the toogle button while taking into account that its state could have been altered by the keyboard shortcut? Can I use bindings?
I managed to implement the continuous update of the button title as follows. I added a programmatic binding for the state (in the example buttonTitle). Notice, that the IBAction toggleButtonTitle: does not directly change the button title! Instead the updateButtonTitle method is responsible for this task. Since self.setButtonTitle is called the aforementioned binding gets updated immediately.
The following example shows what I tried to describe.
// BindThisAppDelegate.h
#import <Cocoa/Cocoa.h>
#interface BindThisAppDelegate : NSObject<NSApplicationDelegate> {
NSWindow* m_window;
NSButton* m_button;
NSString* m_buttonTitle;
NSUInteger m_hitCount;
}
#property (readwrite, assign) IBOutlet NSWindow* window;
#property (readwrite, assign) IBOutlet NSButton* button;
#property (readwrite, assign) NSString* buttonTitle;
- (IBAction)toggleButtonTitle:(id)sender;
#end
And the implementation file:
// BindThisAppDelegate.m
#import "BindThisAppDelegate.h"
#interface BindThisAppDelegate()
- (void)updateButtonTitle;
#end
#implementation BindThisAppDelegate
- (id)init {
self = [super init];
if (self) {
m_hitCount = 0;
[self updateButtonTitle];
}
return self;
}
#synthesize window = m_window;
#synthesize button = m_button;
#synthesize buttonTitle = m_buttonTitle;
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
[self.button bind:#"title" toObject:self withKeyPath:#"buttonTitle" options:nil];
}
- (IBAction)toggleButtonTitle:(id)sender {
m_hitCount++;
[self updateButtonTitle];
}
- (void)updateButtonTitle {
self.buttonTitle = (m_hitCount % 2 == 0) ? #"Even" : #"Uneven";
}
#end
If you store your state in an enum or integer a custom NSValueTransformer will help you to translate a state into its button title equivalent. You can add the NSValueTransformer to the binding options.
NSDictionary* options = [NSDictionary dictionaryWithObject:[[CustomValueTransformer alloc] init] forKey:NSValueTransformerBindingOption];
[self.button bind:#"title" toObject:self withKeyPath:#"buttonTitle" options:options];

Resources