i am currently facing a hard memory issue while using the new iOS7 MKPolygonRenderer class.. I pinpointed the source of the issue to a single line of code:
[renderer invalidatePath];
it seems like the core framework is not releasing the memory here, so that subsequent calls to this function result in an application crash due to memory exceptions.
basically what i want to do is to let the user alter a single polygon overlay on the map.
#interface MapViewController () <MKMapViewDelegate>
{
MKPolygonRenderer* renderer;
}
#end
#implementation MapViewController
- (id) init
{
if ((self=[super init]) != nil)
{
MKMapView* viewMap = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
viewMap.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
viewMap.delegate = self;
[self.view addSubview:viewMap];
viewMap.region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(49.391759,8.675766), MKCoordinateSpanMake(0.35, 0.35));
// a simple polygon overlay with 4 points
CLLocationCoordinate2D overlayCoords[4] = { {49.421247,8.607101}, {49.418121,8.745117}, {49.321094,8.734818}, {49.320199,8.613968}/*, {49.370199,8.583968} */};
MKPolygon* overlay = [MKPolygon polygonWithCoordinates:overlayCoords count:4];
[viewMap addOverlay:overlay];
// the gesture recognizer which is used to alter the polygon
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress_recognized:)];
longPressRecognizer.minimumPressDuration = 0.1f;
[viewMap addGestureRecognizer:longPressRecognizer];
}
return self;
}
- (MKOverlayRenderer*)mapView:(MKMapView*)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
// reuse the renderer if already existent
if (self->renderer == nil)
{
NSLog(#" renderer is nil ==> creating");
renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
renderer.fillColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.4];
renderer.strokeColor = [UIColor greenColor];
renderer.lineWidth = 1;
}
else
NSLog(#" renderer is not nil ==> reusing");
return self->renderer;
}
- (void) longPress_recognized:(UILongPressGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateBegan)
{
// begin drag
// check if and if yes, which polygon point is touched
// set indexOfSelectedPoint
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
// drag move
if (indexOfSelectedMapPoint == -1)
return;
// get the coordinate of the user touch location
CLLocationCoordinate2D c = [self->viewMap convertPoint:[sender locationInView:self->viewMap] toCoordinateFromView:self->viewMap];
// update the coordinate of touched polygon point
self->renderer.polygon.points[indexOfSelectedMapPoint] = MKMapPointForCoordinate(c);
// this line causes the trouble
[renderer invalidatePath];
}
else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
{
// drag end
// reset states
}
}
my test project is using ARC. The profiler is not complaining about memory leaks.
is anybody having similar issues?
am i doing something completely wrong here?
is there a better way of doing this?
thanks for the help in advance
Related
I'm trying to reorganize HelloWorld project in cocos2d to our needs.
The thing I did - made a class, which is inherent from CCPhysicsSprite and wanted to add it to CCLayer (HelloWorldLayer). But something goes wrong. According to debugger my instance is created, but I can't see it in the iOS emulator. Need your help and explanations.
HelloWorldLayer.h
#interface HelloWorldLayer : CCLayer <GKAchievementViewControllerDelegate, GKLeaderboardViewControllerDelegate>
{
CCTexture2D *spriteTexture_; // weak ref
b2World* world_; // strong ref
GLESDebugDraw *m_debugDraw; // strong ref
}
HelloWorldLayer.mm (only changed by me functions:)
-(id) init
{
if( (self=[super init])) {
// enable events
self.touchEnabled = YES;
self.accelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;
// init physics
[self initPhysics];
// create reset button
//[self createMenu];
//Set up sprite
//#if 1
// // Use batch node. Faster
// CCSpriteBatchNode *parent = [CCSpriteBatchNode batchNodeWithFile:#"blocks.png" capacity:100];
// spriteTexture_ = [parent texture];
//#else
// // doesn't use batch node. Slower
// spriteTexture_ = [[CCTextureCache sharedTextureCache] addImage:#"blocks.png"];
// CCNode *parent = [CCNode node];
//#endif
// [self addChild:parent z:0 tag:kTagParentNode];
//
//
// [self addNewSpriteAtPosition:ccp(s.width/2, s.height/2)];
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Tap screen" fontName:#"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( s.width/2, s.height-50);
[self scheduleUpdate];
}
return self;
}
-(void) addNewSpriteAtPosition:(CGPoint)p
{
CCLOG(#"Add sprite %0.2f x %02.f",p.x,p.y);
if([self getChildByTag:kTagParentNode] == nil)
{
BloodRobotUnit *unit = [[BloodRobotUnit alloc] initWithOwner:world_ at:p];
[self addChild:unit z:0 tag:kTagParentNode];
}
}
And Creating unit: (header and mm file:)
#interface BloodRobotUnit : CCPhysicsSprite
{
b2Body *body_;
b2World *owner_;
}
-(id) initWithOwner:(b2World*)owner at:(CGPoint)pt;
mm:
-(id) initWithOwner:(b2World*)owner at:(CGPoint)pt
{
if(self = [super initWithFile:#"blocks.png" rect:CGRectMake(0, 0, 32, 32)])
{
owner_ = owner;
//create body at position
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(pt.x/PTM_RATIO, pt.y/PTM_RATIO);
body_ = owner->CreateBody(&bodyDef);
// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body_->CreateFixture(&fixtureDef);
[self setB2Body: body_];
[self setPosition:pt];
return (self);
}
return nil;
}
Where is my mistake? Any help will be very appreciated
The trick was in setting position to self
Debugger showed that my texture was at position inf:inf
Code change to make everything work is the following:
in mm file of creating a CCPhysicsSprite iheriter do the following:
[self setB2Body: body_];
self.PTMRatio = PTM_RATIO;
//[self setPosition:CGPointMake(pt.x/PTM_RATIO, pt.y/PTM_RATIO)];
That is - you need to set only body position and set PTMRatio (thanx to #giorashc). Setting sprite texture is not necessary.
i am developing a ER diagram editor, i have a bunch of draggable UILabels but all of them have the same name. i want to be able to create a line between two UIlabels when both are pressed together using the long press gesture recognizer. any help will be most appreciated
You can create your long press gesture on the superview shared by these two labels, e.g.:
UILongPressGestureRecognizer *twoTouchLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(handleLongPress:)];
twoTouchLongPress.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:twoTouchLongPress];
You can then write a gesture handler:
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
if ((CGRectContainsPoint(self.label0.frame, location0) && CGRectContainsPoint(self.label1.frame, location1)) ||
(CGRectContainsPoint(self.label1.frame, location0) && CGRectContainsPoint(self.label0.frame, location1)))
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
In the updated comments, you suggest that you're creating a local UILabel variable, and then adding the resulting label to the view. That's fine, but you really want to maintain a backing model, that captures what you're doing in the view. For simplicity's sake, let me assume that you'll have array of these labels, e.g.:
#property (nonatomic, strong) NSMutableArray *labels;
Which you then initialize at some point (e.g. viewDidLoad):
self.labels = [[NSMutableArray alloc] init];
Then as you add labels to your view, add a reference to them in your array:
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(xVal, yVal, 200.0f, 60.0f)];
label.text = sentence;
label.layer.borderColor = [UIColor blueColor].CGColor;
label.layer.borderWidth = 0.0;
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont systemFontOfSize:19.0f];
[self.view addSubview:label];
[self.labels addObject:label];
Then, your gesture can do something like:
- (UILabel *)labelForLocation:(CGPoint)location
{
for (UILabel *label in self.labels)
{
if (CGRectContainsPoint(label.frame, location))
return label; // if found one, return that `UILabel`
}
return nil; // if not, return nil
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
UILabel *label0 = [self labelForLocation:location0];
UILabel *label1 = [self labelForLocation:location1];
if (label0 != nil && label1 != nil && label0 != label1)
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
Frankly, I'd rather see this backed by a proper model, but that's a more complicated conversation beyond the scope of a simple Stack Overflow answer. But hopefully the above gives you an idea of what it might look like. (BTW, I just typed in the above without assistance of Xcode, so I'll apologize in advance for typos.)
I'm currently developing an iPad application that is using ZBarSDK for reading QR Codes.
I am using ZBarReaderView (NOT UIViewController), so i'm showing the front facing camera in a UIView that is drawn using CGRect (see code below).
My problem is that the camera comes up on it's side within the view. The iPad app will ONLY be developed to work in Landscape mode.
When the UIView comes up now, the image is on it's side and i'm not sure how to fix it. I tried using CGAffineTransform which successfully changes the angle somewhat but how will this handle if the iPad is flipped the other way?
I saw some other posts about this similar issue but no resolution that has worked for me. Worth noting i'm using IOS6 and please remember i'm using ZBarReaderView, so the supportedOrientationsMask is NOT available.
I want the user to see the camera view in the upright position regardless of whether landscape is left or right. Right now though, regardless of which way it is turned, the video is sideways (so I can see myself at 90 degrees to the side). The UIView doesn't change at all when the screen is turned either. I'm a bit lost.
Code:
- (void) viewDidAppear: (BOOL) animated {
ZBarReaderView * readerView = [ZBarReaderView new];
ZBarImageScanner * scanner = [ZBarImageScanner new];
[scanner setSymbology: ZBAR_I25
config: ZBAR_CFG_ENABLE
to: 0];
[readerView initWithImageScanner:scanner];
readerView.readerDelegate = self;
readerView.tracksSymbols = YES;
readerView.frame = CGRectMake(20,220,230,230);
readerView.torchMode = 0;
readerView.device = [self frontFacingCameraIfAvailable];
[readerView start];
[self.view addSubview: readerView];
}
-(void)relocateReaderPopover:(UIInterfaceOrientation)toInterfaceOrientation {
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
readerView.previewTransform = CGAffineTransformMakeRotation(M_PI_2);
} else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
readerView.previewTransform = CGAffineTransformMakeRotation(-M_PI_2);
} else if (toInterfaceOrientation== UIInterfaceOrientationPortraitUpsideDown) {
readerView.previewTransform = CGAffineTransformMakeRotation(M_PI);
} else {
readerView.previewTransform = CGAffineTransformIdentity;
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
return YES;
} else if (interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
return YES;
} else {
return NO;
}
}
- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) orient
duration: (NSTimeInterval) duration
{
if (orient == UIInterfaceOrientationLandscapeLeft) {
[readerView willRotateToInterfaceOrientation: orient
duration: duration];
}
if (orient == UIInterfaceOrientationLandscapeRight) {
[readerView willRotateToInterfaceOrientation: orient
duration: duration];
}
}
- (BOOL)shouldAutorotate {
UIInterfaceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIInterfaceOrientationLandscapeLeft) {
return YES;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
return YES;
} else {
return NO;
}
}
-(NSUInteger) supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
I got the same issue and resolved it symply !
I just needed to call :
[reader willRotateToInterfaceOrientation: toInterfaceOrientation duration: duration];
within the willRotate... of the view controller that contains the ZbarReaderView.
Okay I got the orientation working now. No idea why it works now but here's the end code for the readerView part:
readerView = [ZBarReaderView new];
ZBarImageScanner * scanner = [ZBarImageScanner new];
[scanner setSymbology: ZBAR_I25
config: ZBAR_CFG_ENABLE
to: 0];
[readerView initWithImageScanner:scanner];
readerView.readerDelegate = self;
readerView.tracksSymbols = YES;
readerView.frame = CGRectMake(20,220,230,230);
readerView.torchMode = 0;
readerView.device = [self frontFacingCameraIfAvailable];
[self relocateReaderPopover:[self interfaceOrientation]];
[readerView start];
[self.view addSubview: readerView];
Notice the only real difference there is this line:
ZBarReaderView * readerView = [ZBarReaderView new];
Changing that line has fixed the initial orientation issue. No idea why but it did. Now I just need to fix the rotation stuff for if the user rotates the iPad.
This worked fine for me:
// ADD: present a barcode reader that scans from the camera feed
ZBarReaderViewController *reader = [ZBarReaderViewController new];
reader.readerDelegate = self;
//reader.supportedOrientationsMask = ZBarOrientationMaskAll;
ScanOverlay *overlayController = [[ScanOverlay alloc] initWithNibName:#"ScanOverlay" bundle:nil];
reader.cameraOverlayView = overlayController.view;
ZBarImageScanner *scanner = reader.scanner;
// TODO: (optional) additional reader configuration here
reader.supportedOrientationsMask = ZBarOrientationMask(UIInterfaceOrientationLandscapeRight||UIInterfaceOrientationLandscapeLeft);
reader.wantsFullScreenLayout = YES;
reader.showsZBarControls = NO; //If we don't set this to NO, the overlay may not display at all
reader.tracksSymbols = YES;
//To support Landscape orientation
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIDeviceOrientationLandscapeLeft == orientation) {
//Rotate 90
reader.cameraViewTransform = CGAffineTransformMakeRotation (3*M_PI/2.0);
} else if (UIDeviceOrientationLandscapeRight == orientation) {
//Rotate 270
reader.cameraViewTransform = CGAffineTransformMakeRotation (M_PI/2.0);
}
I have attempted to insert a Cordova CleaverView (CDVViewController) into a UIViewController(SenchaViewController) instantiated from a storyboard in order to render a javascript Sencha based view.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (![ self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
CDVViewController *cdvc = [CDVViewController new];
cdvc.wwwFolderName = #"www";
cdvc.startPage = #"sencha.html";
cdvc.useSplashScreen = NO;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
cdvc.view.frame = CGRectMake(0, 0, 768, 1004);
} else {
cdvc.view.frame = CGRectMake(0, 0, 320, 460);
}
[self.view addSubview:cdvc.view];
[self.view bringSubviewToFront:self.menuButton];
[self.view bringSubviewToFront:self.favButton];
cdvc = nil;
NSString *jsSetIdString = [NSString stringWithFormat:#"fetchGuid = function(){return '%#';}", [player valueForKey:#"playerID"]];
[[cdvc webView] stringByEvaluatingJavaScriptFromString:jsSetIdString];
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
}
My problem is after I remove the parent ViewController(SenchaViewController) which owns the CDVViewController's view instance, the CDVViewController does not get released and is still running in the background as I can still see javascript logging in the console. The causes for me to have several CDVViewController all running at once.
This is how the app adds the Parent View Controller:
- (void)renderPlayer:(NSNotification*)notification {
SenchaViewController* newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SenchaView"];
NSDictionary *player = notification.object;
// Set active player
if( player != nil && [player valueForKey:#"playerID"] != #"" ){
// Attach Player Dictionary to Sencha View
newTopViewController.player = player;
// Send it to the top
CGRect frame = self.slidingViewController.topViewController.view.frame;
self.slidingViewController.topViewController = newTopViewController;
self.slidingViewController.topViewController.view.frame = frame;
}
This is how the Parent View controller which owns the CDVController is being removed
_topViewController.view removeFromSuperview];
[_topViewController willMoveToParentViewController:nil];
[_topViewController removeFromParentViewController];
Am I missing something? I was assuming ARC would dealloc this for me
Cordova has some dirty aspects, and this is one of them. Removing a CDVViewController is not enough to get it released. You have to also call the [controller dispose] method.
I have two animations in a custom UIView, anim1 and anim2. Anim1 sets its delegate to self and there is an animationDidStop method in my class which triggers Anim2. If I want something else to occur when Anim2 finishes, how do I do this? Can I specify a delegate method with a different name?
UPDATE
I declare two animations as iVars:
CABasicAnimation *topFlip;
CABasicAnimation *bottomFlip;
I build each animation and set delgate to self e.g.
- (CABasicAnimation *)bottomCharFlap: (CALayer *)charLayer
{
bottomFlip = [CABasicAnimation animationWithKeyPath:#"transform"];
charLayer.transform = CATransform3DMakeRotation(DegreesToRadians(0), 1, 0, 0); //set to end pos before animation
bottomFlip.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(DegreesToRadians(-360), 1, 0, 0)];
bottomFlip.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(DegreesToRadians(-270), 1, 0, 0)];
bottomFlip.autoreverses = NO;
bottomFlip.duration = 0.5f;
bottomFlip.repeatCount = 1;
bottomFlip.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
bottomFlip.delegate = self;
bottomFlip.removedOnCompletion = FALSE;
return bottomFlip;
}
I then try and find bottomFlip in animationdidStop:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
if (theAnimation == bottomFlip) {
NSLog(#"Bottom Animation is: %#", bottomFlip);
}
NSLog(#"Animation %# stopped",theAnimation);
[bottomHalfCharLayerFront addAnimation:[self bottomCharFlap:bottomHalfCharLayerFront] forKey:#"bottomCharAnim"];
bottomHalfCharLayerFront.hidden = NO;
topHalfCharLayerFront.hidden = YES;
//insert the next one???
}
"Animation stopped" is logged but nothing else i.e. it doesn't seem to recognise the bottomFlip iVar
This seems to work:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
//NSLog(#"First Animation stopped");
if (anim ==[topHalfCharLayerFront animationForKey:#"topCharAnim"]) {
NSLog(#"Top Animation is: %#", anim);
topHalfCharLayerFront.hidden = YES;
[bottomHalfCharLayerFront addAnimation:[self bottomCharFlap:bottomHalfCharLayerFront] forKey:#"bottomCharAnim"];
bottomHalfCharLayerFront.hidden = NO;
}
else if ((anim ==[bottomHalfCharLayerFront animationForKey:#"bottomCharAnim"])) {
NSLog(#"Bottom Animation is: %#", anim);
}
Just hold onto a reference to your animations as ivars and compare their memory addresses to the one that gets handed off to animationDidStop:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
if (theAnimation == anim1)
{
// Spin off anim2
}
else
{
// anim2 stopped. Make something else occur
}
}