How to add a method with multiple parameters to the selector of a button - button

I get this error Terminating app due to uncaught exception 'NSInvalidArgumentException when I try to add a method with multiple parameters to the selector of a button
[viewButton addTarget:self action:#selector(loadDocument: inView:) withObject:cerereIndemnizatie.fisier withObject:self.myWebview forControlEvents:UIControlEventTouchUpInside];
This is the method
-(void)loadDocument:(NSString*)documentName inView:(UIWebView*)webView
{
NSString *path = [[NSBundle mainBundle] pathForResource:documentName ofType:#"pdf"];
NSURL *url = [NSURL fileURLWithPath:path];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
}

I don't know why you think you can do this or why would UIButton respond to the addTarget:selector:withObject:withObject:forControlEvents: selector... (Seriously, don't guess, read the documentation...)
You can achieve similar behavior by using associated objects:
UIButton *btn = // create the button
objc_setAssociatedObject(btn, "firstObject", someObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(btn, "secondObject", otherObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:#selector(click:) forControlEvents:UIControlEventTouchUpInside];
- (void)click:(UIButton *)sender
{
id first = objc_getAssociatedObject(btn, "firstObject");
id second = objc_setAssociatedObject(btn, "secondObject");
// etc.
}

Related

Repeating Audio in WatchKit (AVAudioPlayer?)?

I am wanting to loop a local audio file in my Apple Watch App. Currently I am using AVAudioPlayerNode and AVAudioEngine which works well but I cannot figure out how to loop the sound.
I noticed that I can use AVAudioPlayer, which has the handy "numberOfLoops" but, for some reason AVAudioPlayer is not working on the watch. I have no idea why.
Here is my current code to play a sound:
_audioPlayer = [[AVAudioPlayerNode alloc] init];
_audioEngine = [[AVAudioEngine alloc] init];
[_audioEngine attachNode:_audioPlayer];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_audioEngine connect:_audioPlayer to:_audioEngine.mainMixerNode format:stereoFormat];
if (!_audioEngine.isRunning) {
NSError* error;
[_audioEngine startAndReturnError:&error];
}
NSError *error;
NSBundle* appBundle = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[appBundle pathForResource:#"FILE_NAME" ofType:#"mp3"]];
AVAudioFile *asset = [[AVAudioFile alloc] initForReading:url error:&error];
[_audioPlayer scheduleFile:asset atTime:nil completionHandler:nil];
[_audioPlayer play];
Here is the code i've tried to use for AVAudioPlayer, but does not work:
NSError *audioError;
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FILE_NAME" ofType:#"mp3"]] error:&audioError];
player.numberOfLoops = MAXFLOAT;
player.delegate = self;
[player play];
I am using WatchKit 5.0(+).
You can loop your AVAudioFile by recursively scheduling it:
__block __weak void (^weakSheduleFile)(void);
void (^scheduleFile)(void);
weakSheduleFile = scheduleFile = ^{ [self->_audioPlayer scheduleFile:asset atTime:nil completionHandler:weakSheduleFile]; };
scheduleFile();
I'm not sure if this will be a seamless loop. If it's not, you can try always having two files scheduled:
scheduleFile();
scheduleFile();
If your audio file fits into memory, you could schedule playback as an AVAudioBuffer with the AVAudioPlayerNodeBufferLoops option (N.B. only tested on simulator!):
AVAudioFormat *outputFormat = [_audioPlayer outputFormatForBus:0];
__block AVAudioPCMBuffer *srcBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:asset.processingFormat frameCapacity:(AVAudioFrameCount)asset.length];
if (![asset readIntoBuffer:srcBuffer error:&error]) {
NSLog(#"Read error: %#", error);
abort();
}
AVAudioPCMBuffer *dstBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)asset.length];
AVAudioConverter *converter = [[AVAudioConverter alloc] initFromFormat:srcBuffer.format toFormat:dstBuffer.format];
AVAudioConverterOutputStatus status = [converter convertToBuffer:dstBuffer error:&error withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus * _Nonnull outStatus) {
if (srcBuffer) {
AVAudioBuffer *result = srcBuffer;
srcBuffer = NULL;
*outStatus = AVAudioConverterInputStatus_HaveData;
return result;
} else {
*outStatus = AVAudioConverterInputStatus_EndOfStream;
return NULL;
}
}];
assert(status != AVAudioConverterOutputStatus_Error);
[_audioPlayer scheduleBuffer:dstBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_audioPlayer play];

Loading an audio sample from the documents folder

I'm trying to have an app play audio files added via itunes file-sharing. I've managed the app to retrieve the app's sandbox folder's content, but I'm not able to load such files into a C4Sample by specifying the complete path.
NSString *documentsFolder;
NSString *clickAudioPath;
C4Sample *clicksample;
-(void)setup {
// Retrieve the app's Documents folder
documentsFolder = [self applicationDocumentsDirectory];
clickAudioPath = [NSString stringWithFormat:#"%#/click.mp3", documentsFolder];
// Add test click audio
clicksample = [C4Sample sampleNamed:clickAudioPath];
[clicksample prepareToPlay];
[clicksample play];
}
// Get Documents folder
- (NSString *) applicationDocumentsDirectory{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
The above code doesn't play any sound, although I've doubled checked that clicksample actually refers to an existing file. How can I specify a complete path instead of just a name to load the audio?
Add a new method as below.
-(id) initWithURL:(NSURL *) soundFileURL
{
self = [super init];
if(self != nil) {
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
self.enableRate = YES;
self.player.delegate = self;
[self setup];
}
return self;
}

Saving an ALAsset to SQLite3 database - retrieve error

I'm trying to save a list of assets to upload in a sqllite3 db, but when i parse the database and set the assets to an array, then try to use the asset i get a SIGABRT error.
ALAsset *asset = (ALAsset *) assets[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"image%d: ready to upload.",indexPath.row];
cell.detailTextLabel.text = #"1.3MB to folder <server folder>";
[[cell imageView] setImage:[UIImage imageWithCGImage:[asset thumbnail]]];// SIGABRT ERROR
Im saving the ALAsset to the database as a string (TEXT) with UTF8formatting
NSMutableArray *tmpArray = [NSMutableArray alloc]init];
///get sql
[tmpArray addObject:someStringFromSQL];
///end sql loop
assets = [tmpArray mutableCopy];
in the code above I tried:
[[cell imageView] setImage:[UIImage imageWithCGImage:[(ALAsset *) asset thumbnail]]];// SIGABRT ERROR
and that didn't work.
This is the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString thumbnail]: unrecognized selector sent to instance 0xc0a7800'
Any suggestions?
Also as a side question: Does anyone know how to get the file size (i.e. 1.3MB) from the asset?
BLOCK:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//do stuff in cell
NSURL *aURL =[NSURL URLWithString:[assets objectAtIndex:indexPath.row]];
[assetsLibrary assetForURL:aURL resultBlock:^(ALAsset *asset){
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];
});
[[NSNotificationCenter defaultCenter] postNotificationName:#"newAssetImageRetrieved" object:nil];
//in this notificaton I'm reloading the data; its putting the tableview in an infinite loop - but the images display...
}
failureBlock:^(NSError *error){
// error handling
NSLog(#"Can't get to assets: FAILED!");
}];
//cell.imageView.image = [UIImage imageWithCGImage:[asset thumbnail]];
cell.textLabel.text = [NSString stringWithFormat:#"image%d: ready to upload.",indexPath.row];
cell.detailTextLabel.text = [NSString stringWithFormat:#"1.3MB to folder %#", [destinations objectAtIndex:indexPath.row]];
//[[cell imageView] setImage:[UIImage imageWithCGImage:[asset thumbnail]]];
return cell;
}
There are a couple of issues with your code sample:
The image retrieval is happening asynchronously, so when you try to update the image, you want to make sure the cell is still visible (and not reused for another NSIndexPath).
In this case, the retrieval from the ALAssetsLibrary will probably be so fast that this isn't critical, but it's a good pattern to familiarize yourself with, because if you're ever retrieving images over the Internet, this issue becomes increasingly important.
Because cells are being reused, if you don't find the image immediately and have to update it asynchronously, make sure you reset the UIImageView before initiating the asynchronous process. Otherwise, you'll see a "flickering" of replacing old images with new ones.
You are using UITableViewCell for your cell. The problem with that is that it will layout the cell based upon the size of the image present by the time cellForRowAtIndexPath finishes.
There are two easy solutions to this. First, you could initialize the cell's imageView to be a placeholder image of the correct size. (I usually have an image called placeholder.png that is all white or all transparent that I add to my project, which is what I used below.) This will ensure that cell will be laid out properly, so that when you asynchronously set the image later, the cell will be laid out properly already.
Second, you could alternatively use a custom cell whose layout is fixed in advance, bypassing this annoyance with the standard UITableViewCell, whose layout is contingent upon the initial image used.
I'd suggest using a NSCache to hold the thumbnails images. That will save you from having to constantly re-retrieve the thumbnail images as you get them from your ALAssetsLibrary as you scroll back and forth. Unfortunately, iOS 7 broke some of the wonderful NSCache memory-pressure logic, so I'd suggest a cache that will respond to memory pressure and purge itself if necessary.
Anyway, putting that all together, you get something like:
#interface ViewController ()
#property (nonatomic, strong) NSMutableArray *assetGroups;
#property (nonatomic, strong) ALAssetsLibrary *library;
#property (nonatomic, strong) ThumbnailCache *imageCache;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageCache = [[ThumbnailCache alloc] init];
self.assetGroups = [NSMutableArray array];
self.library = [[ALAssetsLibrary alloc] init];
[self.library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (!group) {
[self.tableView reloadData];
return;
}
CustomAssetGroup *assetGroup = [[CustomAssetGroup alloc] init];
assetGroup.name = [group valueForProperty:ALAssetsGroupPropertyName];
assetGroup.assetURLs = [NSMutableArray array];
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[assetGroup.assetURLs addObject:[result valueForProperty:ALAssetPropertyAssetURL]];
}
}];
[self.assetGroups addObject:assetGroup];
} failureBlock:^(NSError *error) {
NSLog(#"%s: enumerateGroupsWithTypes error: %#", __PRETTY_FUNCTION__, error);
}];
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.assetGroups.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
CustomAssetGroup *group = self.assetGroups[section];
return [group.assetURLs count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
CustomAssetGroup *group = self.assetGroups[section];
return group.name;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// note, these following three lines are unnecessary if you use cell prototype in Interface Builder
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
CustomAssetGroup *group = self.assetGroups[indexPath.section];
NSURL *url = group.assetURLs[indexPath.row];
NSString *key = [url absoluteString];
UIImage *image = [self.imageCache objectForKey:key];
if (image) {
cell.imageView.image = image;
} else {
UIImage *placeholderImage = [UIImage imageNamed:#"placeholder.png"];
cell.imageView.image = placeholderImage; // initialize this to a placeholder image of the right size
[self.library assetForURL:url resultBlock:^(ALAsset *asset) {
UIImage *image = [UIImage imageWithCGImage:asset.thumbnail]; // note, use thumbnail, not fullResolutionImage or anything like that
[self.imageCache setObject:image forKey:key];
// see if the cell is still visible, and if so, update it
// note, do _not_ use `cell` when updating the cell image, but rather `updateCell` as shown below
UITableViewCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath]; // not to be confused with similarly named table view controller method ... this one checks to see if cell is still visible
if (updateCell) {
[UIView transitionWithView:updateCell.imageView duration:0.1 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
updateCell.imageView.image = image;
updateCell.textLabel.text = asset.defaultRepresentation.filename;
} completion:nil];
}
} failureBlock:^(NSError *error) {
NSLog(#"%s: assetForURL error: %#", __PRETTY_FUNCTION__, error);
}];
}
return cell;
}
#end
The above uses the following classes:
/** Thumbnail cache
*
* This cache optimizes retrieval of old thumbnails. This purges itself
* upon memory pressure and sets a default countLimit.
*/
#interface ThumbnailCache : NSCache
// nothing needed here
#end
#implementation ThumbnailCache
/** Initialize cell
*
* Add observer for UIApplicationDidReceiveMemoryWarningNotification, so it purges itself under memory pressure
*/
- (instancetype)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
self.countLimit = 50;
};
return self;
}
/** Dealloc
*
* Remove observer before removing cache
*/
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
#end
and
/** Custom AssetGroup object
*
* This is my model object for keeping track of the name of the group and list of asset URLs.
*/
#interface CustomAssetGroup : NSObject
#property (nonatomic, copy) NSString *name;
#property (nonatomic, strong) NSMutableArray *assetURLs;
#end
#implementation CustomAssetGroup
// nothing needed here
#end
You have to explore all the code base related to the save and retrieve functionality.
However, here are some good tips.
Save the ALAsset Url instead of saving the entire ALAsset as a string.
Retrieve the ALAsset Url from the database and convert it to NSUrlString.
Use ALAsset Library to load the image or thumbnail back.
Hope this will help you.

get image from asyncimageview

I have a little problem, I use this class for load some image asincronusly
#import <UIKit/UIKit.h>
#interface AsyncImageView : UIView {
NSURLConnection* connection;
NSMutableData* data;
}
- (void)loadImageFromURL:(NSURL*)url;
- (UIImage*) image;
#end
#import "AsyncImageView.h"
#implementation AsyncImageView
- (void)dealloc {
[connection cancel]; //in case the URL is still downloading
}
- (void)loadImageFromURL:(NSURL*)url {
//in case we are downloading a 2nd image
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; //notice how delegate set to self object
}
//the URL connection calls this repeatedly as data arrives
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil) { data = [[NSMutableData alloc] initWithCapacity:2048]; }
[data appendData:incrementalData];
}
//the URL connection calls this once all the data has downloaded
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
//so self data now has the complete image
connection=nil;
if ([[self subviews] count]>0) {
//then this must be another image, the old one is still in subviews
[[[self subviews] objectAtIndex:0] removeFromSuperview]; //so remove it (releases it also)
}
//make an image view for the image
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:data]];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:imageView];
imageView.frame = self.bounds;
[imageView setNeedsLayout];
[self setNeedsLayout];
data=nil;
}
- (UIImage*) image {
UIImageView* iv = [[self subviews] objectAtIndex:0];
return [iv image];
}
#end
and this for load the image
AsyncImageView *asyncImage = [[AsyncImageView alloc] initWithFrame:frame];
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString:indirizzoImmagine];
[asyncImage loadImageFromURL:url];
[self.view addSubView:asyncImage]
with this code all work but when I try to put asyncImage on [cell.imageview setImage:async.image]; the app crash, I think that I need to change the subclass in uiimageview but nothing...the same error
Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array'
How can I geto only the image?
In AsyncImageView class synthesize imageCache using following code you can get the image
[imageView.imageCache imageForKey:str_ImgUrl];
Here imageView is the object of AsyncImageView.

NSException error seems to occur randomly

This code was working fine, now for some reason I'm getting an NSException error that's generating a SIGABRT at the following line of code...
self.popoverController = [[[UIPopoverController alloc] initWithContentViewController:wbPopOverController] autorelease];
Here's the method it's located in...
-(IBAction)showPop:(id)sender {
//close any open popovers
[self.popoverController dismissPopoverAnimated:NO];
// Initialize wbPopUpViewController
PopoverAircraftClass *wbPopOverController = [[PopoverAircraftClass alloc] initWithNibName:#"PopoverAircraftClass" bundle:[NSBundle mainBundle]];
wbPopOverController.contentSizeForViewInPopover = CGSizeMake(175, 216);
wbPopOverController.delegate = self; //for update
[wbPopOverController setAircraftList:aircraftList withCurrentValue:activeRegistration];
// Object converted to UIPopOverController (retained so we can reference it)
self.popoverController = [[[UIPopoverController alloc] initWithContentViewController:wbPopOverController] autorelease];
self.popoverController.passthroughViews = [NSArray arrayWithObject: self.view];
[self.popoverController presentPopoverFromRect:[sender frame] inView:self.layoutViewIB permittedArrowDirections:UIPopoverArrowDirectionUp animated:NO];
[wbPopOverController release];
}
I'm new at this as you can probably tell. Any ideas as to what's generating the NSException?

Resources