I'm still on a learning curve with Obj-c.
I'm trying to override the setColumnCount method in the TTThumbsTableViewCell from Three20,
but I don't know why It wont work.
In the method setColumnCount from the super class (TTThumbsTableViewCell) I've added a NSLog(#"Test Super") and the subclass of TTThumbsTableViewCell in setColumnCount method I've add NSLog(#"Test Sub") and the subclass method never prints out "Test Sub".
I'm not sure if I'm overriding correctly in Obj-C, basically I copy the whole method setColumnCount from TTThumbsTableViewCell to the subClass and modify it there, but it still doesn't run the overridden method in the subclass.
Is there something I'm missing?
Im working with XCode 4
Thanks in advance
Brett
South Africa
I have a TTThumbsTableViewCell subClass called GridImage
#interface GridImage : TTThumbsTableViewCell '<'TTPhoto>{
//Code...
#end
The .m override's the setColumnCount method
'#'import "GridImage.h"
#implementation GridImage
(void)setColumnCount:(NSInteger)columnCount {
NSLog(#"Test Sub");
if (_columnCount != columnCount) {
for (TTThumbView* thumbView in _thumbViews) {
[thumbView removeFromSuperview];
}
[_thumbViews removeAllObjects];
_columnCount = columnCount;
for (NSInteger i = _thumbViews.count; i < _columnCount; ++i) {
TTThumbView* thumbView = [[[TTThumbView alloc] init] autorelease];
[thumbView addTarget:self action:#selector(thumbTouched:)
forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:thumbView];
[_thumbViews addObject:thumbView];
if (_photo) {
[self assignPhotoAtIndex:_photo.index+i toView:thumbView];
}
}
}
}
Could it be something to do with the '<'TTPhoto> protocol from Three20?
Related
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.
I am using collection view to get data from my server display some of it in a cell and then I have a segue to the destination view controller I got the image to update correctly but for some reason the text is not going to my UITextView
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//DetailSegue
if ([segue.identifier isEqualToString:#"DetailSegue"]) {
ICBCollectionViewCell *cell = (ICBCollectionViewCell *)sender;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
ICBDetailViewController *dvc = (ICBDetailViewController *)[segue destinationViewController];
path = [paths objectAtIndex:indexPath.row];
Path = [path objectForKey:#"path"];
title = [titles objectAtIndex:indexPath.row];
Title = [title objectForKey:#"title"];
sku = [SKUs objectAtIndex:indexPath.row];
Sku = [sku objectForKey:#"SKU"];
longDescrip = [longDescription objectAtIndex:indexPath.row];
LongDescrip = [longDescrip objectForKey:#"longDescrip"];
LongDescrip =#"Hello World";
NSLog(#"Descrip =%#",LongDescrip);
NSString *iconTitle =[NSString stringWithFormat:#"%#.png",Sku];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fullPath = [docDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#",iconTitle]]; //add our image to the path
dvc.img = [[UIImage alloc] initWithContentsOfFile:fullPath];
dvc.title = Title;
//UITextView *descrip = (UITextView *)[cell viewWithTag:120];
[dvc.descrip setText:#"Hello"];
}
}
I am not sure if it has something to do with the fact that the object being sent to is a UITextView and I am sending it a String
or if I have something hooked up wrong
here is the .m and .h for the detailController also
the .h
#import <UIKit/UIKit.h>
#interface ICBDetailViewController : UIViewController
#property(weak) IBOutlet UIImageView *imageView;
#property (strong) UIImage *img;
#property(weak) IBOutlet UITextView *descrip;
#end
the .m
#import "ICBDetailViewController.h"
#interface ICBDetailViewController ()
#end
#implementation ICBDetailViewController
#synthesize imageView, img, descrip;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView.image = self.img;
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Hopefully somebody will see what I have missed for two days
after again reviewing it all I noticed i missed the outlet in the .h and forgot to set it show in the ViewDidLoad of the .m I also missed hooking upthe referencing outlet to my txtView
Silly question. Cocos2d is build around the parent-child hierarchy. I was wondering if it is ok to have a parent class (e.g. GameScene) and initialize a child class (e.g. SpriteHandler) with a pointer to a member of the parent class (e.g. CCSpriteBatchNode*).
I am trying to do this to optimize the number of CCSpriteBatchNodes. Here is a code snippet of my main class (GameScene style).
#import <Foundation/Foundation.h>
#import "cocos2d.h"
enum ShooterSceneLayerTags {
HudLayerTag = 0,
};
#interface ShooterScene : CCLayer {
CCSpriteBatchNode* sharedSpriteBatchNode;
}
-(id) initWithSharedBatchNodeReference:( CCSpriteBatchNode*) sharedSpriteBatchNode;
+ (id) sceneWithId:(int)sceneId;
#end
#import "ShooterScene.h"
#import "MainMenuScene.h"
//Layers
#import "LevelSpritesLayer.h"
#import "HudLayer.h"
#interface ShooterScene (PrivateMethods)
-(void) addLayers:(int)sceneId;
-(void) loadGameArtFile;
-(BOOL) verifyAndHandlePause;
#end
#implementation ShooterScene
+ (id) sceneWithId:(int)sceneId
{
CCScene *scene = [CCScene node];
ShooterScene * shooterLayer = [[self alloc] initWithId:sceneId];
[scene addChild:shooterLayer];
return scene;
}
-(id) initWithId:(int)sceneId
{
if ((self = [super init]))
{
//Load game art before adding layers - This will initialize the batch node
[self loadGameArtFile:sceneId];
//Will add sprites to shared batch node for performance
[self addLayers:sceneId];
[self addChild:sharedSpriteBatchNode];
//Do other stuff..
[self scheduleUpdate];
}
return self;
}
-(void) addLayers:(int)sceneId
{
LevelSpritesLayer * levelData = [LevelSpritesLayer node];
[levelData initWithSharedBatchNodeReference:sharedSpriteBatchNode];
[self addChild:levelData];
switch (sceneId) {
case 1:
[levelData loadLevelOneSprites];
break;
case 2:
[levelData loadLevelTwoSprites];
break;
default:
break;
}
HudLayer * hud = [HudLayer node];
[hud setUpPauseMenu];
[self addChild:hud z:1 tag:HudLayerTag];
}
-(BOOL) verifyAndHandlePause
{
HudLayer * hud = [self getChildByTag:HudLayerTag];
if(hud.pauseRequested){
[[CCDirector sharedDirector] replaceScene:[MainMenuScene scene]];
return true;
}
else {
return false;
}
}
-(void) update:(ccTime)delta
{
if([self verifyAndHandlePause]==false)
{
//Continue with animation etc..
}
}
/**
This is tricky. Could have loaded this in LevelData but as I am expecting to use the same SpriteSheet for HudLayer as well then
I prefer to have the control here of this. Also, the same sheet could be used for more level, hence specific function is not bad
**/
-(void) loadGameArtFile:(int) sceneId
{
CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
[frameCache addSpriteFramesWithFile:#"game-art-hd.plist"];
sharedSpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:#"game-art-hd.png"];
}
//As dealloc is deprecated, I prefer to remove unused sprites and texture on cleanup
-(void) cleanup
{
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
}
And here is one of the loadLevelData methods, it uses the sharedSpriteBAtchNodeReference
-(void) loadLevelOneSprites
{
//Just a proof of concept example
testSprite = [CCSprite spriteWithSpriteFrameName:#"File0.png"];
testSprite.anchorPoint = CGPointMake(0.5f, 0.5f);
testSprite.position = CGPointMake(160.0f, 240.0f);
[sharedSpriteBatchNodeReference addChild:testSprite];
}
You can do that but if you are using ARC you should make your sharedSpriteBatchNode a weak pointer. If you don't you could end up with a circular reference.
What will happen with the circular reference is when the Director releases your game scene after it has finished running your game scene will still have your child retaining it and your game scene will still be retaining that child. This circle will float off and never be able to be released as it is abandoned.
What [Ben] said. It should not be a retaining or strong reference, the latter being the default in ARC for instance variables.
There is one way to ensure under ARC that you're retain-cycle-safe even if you use a strong reference. Override the cleanup method and nil the reference there (under MRC you should also call release here, if you retained the reference):
-(void) cleanup
{
sharedSpriteBatchNode = nil;
[super cleanup];
}
Doing this in dealloc won't work. As long as a child node has a strong reference to a parent, it will not be deallocated. So you need to do this in cleanup, and ensure that all method calls where you can set the cleanup flag has that parameter set to YES.
But there are other, and I think better, solutions than passing a parent node in the initializer. For example you could get the shared batch node via a common tag from the parent, and either do that every time you need it (wrap it into a small function) or store it in a weak (non-retaining) instance var:
// onEnter is typically called right after init (during addChild)
// parent is already set here
-(void) onEnter
{
[super onEnter];
CCSpriteBatchNode* sharedBatchNode = [parent getChildByTag:kSharedBatchNodeTag];
}
Or get the parent and cast it, assuming the sharedBatchNode to be a property of the parent class:
-(void) whereEver
{
ShooterScene* scene = (ShooterScene*)parent;
CCSpriteBatchNode* sharedBatchNode = scene.sharedSpriteBatchNode;
…
// you can also reduce the above to a single line:
CCSpriteBatchNode* batch = ((ShooterScene*)parent).sharedSpriteBatchNode;
}
Especially this latter solution is recommended. Even if you need to do that often it's fast. Casting is free, property access no more than a message send. Just be sure that the parent is in fact an object of the class you're casting it to.
Am writing a simple multi view application with one root controller and two view controllers (blue and yellow). When I try to run it in the iPhone Simulator, I get an error by the #synthesize property. I have commented out the error on that line.
Can you tell me what the error means, and how can I get the app to run?
Thank you.
#import "SwitchViewController.h"
#import "BlueViewController.h"
#import "YellowViewController.h"
#interface SwitchViewController ()
#end
#implementation SwitchViewController
#synthesize yellowViewController;
#synthesize blueViewController; //Thread 1:EXC_BAD_ACCESS(code=2, address=0xbf7ffffc)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView
{
// If you create your views manually, you MUST override this method and use it to create your views.
// If you use Interface Builder to create your views, then you must NOT override this method.
}
- (void)viewDidLoad
{
self.blueViewController = [[BlueViewController alloc]initWithNibName:#"BlueView" bundle:nil];
[self.view insertSubview:self.blueViewController.view atIndex:0];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)switchViews:(id)sender
{
if(self.yellowViewController.view.superview==nil) {
if(self.yellowViewController==nil) {
self.yellowViewController =
[[YellowViewController alloc] initWithNibName:#"YellowView" bundle:nil];
}
[blueViewController.view removeFromSuperview];
[self.view insertSubview:self.yellowViewController.view atIndex:0];
} else {
if (self.blueViewController == nil) {
self.blueViewController =
[[BlueViewController alloc] initWithNibName:#"BlueView" bundle:nil];
}
}
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use
if (self.blueViewController.view.superview == nil) {
self.blueViewController = nil;
} else {
self.yellowViewController = nil;
}
}
#end
Comment out the loadView method in your SwitchViewController, BlueViewController, and YellowViewController. The empty application template was changed to leave these uncommented in recent versions of XCode, but the Beginning iOS Development book you are following used an older version with the stubbed methods pre-commented.
I don't know if it's some setting I accidentally ticked, but tell me how to fix it please:
Whenever I create a new Obj-C class, it automatically looks like:
#import <Foundation/Foundation.h>
#interface MathUtilities : NSObject {
**#private**
}
#end
That line is automatically inserted. It never was there before, but something is not adding it. My files also now come with init and dealloc methods. Did something happen? Also, shouldn't it be importing Cocoa instead of Foundation?
This is XCode 4
There is nothing to fix. XCode is creating stubs for you to fill out your code into. It's a time saver, thats all. It should be generating a header and implementation stub file for you, which you can extend like so:
Your header file (MathUtilities.h):
#import <Foundation/Foundation.h>
#interface MathUtilities : NSObject {
#private:
NSNumber * num;
}
- (void) doSomeWork;
#end
Your implementation file (MathUtilities.m) :
#import "MathUtilities.h"
#implementation MathUtilities
- (id) init {
self = [super init];
if(self) {
// Initialization code here.
}
return self;
}
- (void) dealloc {
[super dealloc];
}
- (void) doSomeWork {
return;
}
#end