How to check if CALayer exists - iphone

I have a CALayer which animates (moves) off-screen on the y axis.
After it's off-screen i'm doing a [myLayer removeFromSuperLayer] so its gone from the view and does not go back to start position.
While the layer is still in the view it can be paused and resumed by pushing a toggle button and this works all fine.
The only thing is that after the [myLayer removeFromSuperLayer] has run my app crashes.
This is caused by the fact the button is trying to pause or resume the layer which doesn't exist anymore.
How can i check if the layer is removed or still in the view?
I thought something like this for the pause part of my toggle button:
if (self.myLayer == nil)
{
// here i want to add the layer again
[self.view.layer addSublayer:myLayer];
// immediately pause it
[self pauseLayer:myLayer];
}
else
{
// just pause no need to create the layer again because it's still there
[self pauseLayer:myLayer];
}
As you might suspect the self.myLayer == nil is not the way to do it, but what is?
Thanks in advance.

Removing a layer from its superlayer will not cause the layer to become nil, which is why your self.myLayer == nil check is not working. However, you could easily set the field to nil when you remove it, like:
[self.myLayer removeFromSuperLayer];
self.myLayer = nil;
Of course, if you need to add it again after that then you'd need to either reassign the layer to self.myLayer (if you have a reference to it that you're keeping somewhere else) or create a new instance of the layer from scratch.

Related

CAShapeLayer.isHidden automatically applying an animation in swift

I'm new to CAShapeLayers so forgive me if this is an obvious fix. Im trying to move a line in sync with a UIScrollView when the view is moving up but keep the line still when the scroll view is moving down. I am using a delegate to detect when the scroll view is moving and this is my code for changing between the visible line.
func scrollViewDidScroll(_ scrollView: UIScrollView){
if(mainScrollView.contentOffset.y < CGFloat(0.0)){
topMainContentViewLine!.isHidden = false
largeRocketImageView!.isHidden = true
topViewLine!.isHidden = true
}else if(mainScrollView.contentOffset.y == CGFloat(0)){
topMainContentViewLine!.isHidden = false
topViewLine!.isHidden = false
}else{
largeRocketImageView!.isHidden = false
topMainContentViewLine!.isHidden = true
topViewLine!.isHidden = false
}
}
When I run my app I can easily see that the two lines are fading in and out instead of appearing and disappearing instantly. For comparison the image has no animation. Any ideas on how to get rid of the animation? Thanks again!
From the documentation:
https://developer.apple.com/documentation/quartzcore/calayer/1410838-ishidden
isHidden
A Boolean indicating whether the layer is displayed. Animatable.
See that word "animatable"? That is a rather tight shorthand; it means that when you set this property on a layer that is not the underlying backing layer of a UIView (e.g. any standalone sublayer), animation is the default. That's called implicit layer animation.
If you do not want animation of an animatable property under those circumstances, you can turn animation off by calling
CATransaction.setDisableActions(true)
You can do that either for a specific explicit transaction block (i.e. everything between matching begin() and commit() commands), or for the entire implicit transaction in which all your code runs.
More commonly, if you have a layer for which you always or mostly don't want animation, you'd host it as the underlying layer of a custom UIView. In that case, setting an animatable property would not animate, and if you needed animation, you would use an explicit CAAnimation (or UIView animation) to get it.
For comparison the image has no animation
Because largeRocketImageView is not, itself, a layer; it's a view.

Subclassed UIView doesn't get tag set

I have subclassed a UIView class and I create multiple instances of this class in a loop (incrementing each time), however when I try to set the tag of the view, and log it to the console after they are all created, they all have a tag of 1, regardless of what I set the tag as.
Any help would be appreciated, Thanks!
My Code for creating the subview is here:
//for() loop above with i as counter
FlashCardView *subview = [[FlashCardView alloc] initWithFrame:frame];
subview.delegate = self;
subview.viewNum=i+10; //My attempt at a workaround but I cannot get the view with this later so it is not very helpful
[subview setTag:i+10]; //Tried this and subview.tag=i+10;
NSLog(#"%d", subview.tag); //Prints correctly
//Gets added to parent later
This NSLog logs the correct tag, however when I log the tag in the UIView subclass, it always returns its tag as 1. Also, if I print all the subviews of the parent in a later called method (In the viewcontroller), all of them have the tag 1.
I cannot tell you why, but I can tell you how to find the problem. In your FlashCardView subclass, add this method:
- (void)setTag:(NSInteger)theTag
{
assert(theTag != 1);
[super setTag:theTag];
}
Then, when whatever is setting the tag to 1 does it, the assert will fire and you can look at the stack trace and see where its coming from.
Alternately, remove the assert, and put a breakpoint on the super message.
PS: make sure you enable exceptions!
Hi thanks for all your help, I found that I had accidentally typed in the subclass's variable when I meant to put in something else, setting the tag to 1. #Sunny, thanks for telling me to double-check.

What is the best best way to disable cross fades on UICollectionView/UICollectionViewLayout rotates or bounds changes?

I have a subclass of UICollectionViewLayout which places cells in a circle. The layout returns YES for the call shouldInvalidateLayoutForBoundsChange:. On rotation, the cell in the initial position fades out and the cell in the final position fades in.
By adding the following code to my layout I can disable the fades and the
circle of items appears to to simply rotate with the orientation change:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
return nil;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
}
Why do the methods get called on a bounds change because documentation doesn't seem to suggest they do? Documentation seems to state they get called related to insertion and removal of items from the collection view.
Is there a better way to disable the cross fade during rotation?
Notes:
The initialLayoutAttributesForAppearingItemAtIndexPath: documentation
states that by default the method returns nil but calls to super returned
non-nil values.
I set symbolic breakspoints on the UICollectionView methods
deleteItemsAtIndexPaths:, moveItemAtIndexPath:toIndexPath: and
insertItemsAtIndexPaths: and none of them are hit during rotation.
The UICollectionViewLayout.h file states
// This set of methods is called when the collection view undergoes an
animated transition such as a batch update block or an animated
bounds change.
// For each element on screen before the invalidation,
finalLayoutAttributesForDisappearingXXX will be called and an
animation setup from what is on screen to those final attributes.
// For each element on screen after the invalidation,
initialLayoutAttributesForAppearingXXX will be called an an
animation setup from those initial attributes to what ends up on
screen.
which clearly says they are called on bounds changes. Rather than removal/insertion, "old state" and "new state" seems more accurate.

Forcing Core Plot plotArea fill to update

I'm trying to toggle the fill for a Core Plot plot area on and off. When I set up the fill at application launch everything works. Toggling the first time works fine. Subsequent toggling of the fill on or off does not work unless a rotate the display.
-(void)layoutGraphPlotAreaFill{
CPTFill *plotFill=nil;
if (self.graphPlotAreaHasFill) {
plotFill=self.graphPlotAreaFill;
}else{
plotFill=nil;
}
self.graph.plotAreaFrame.plotArea.fill=plotFill;
}
I'm calling the above code from a settings view controller via a delegate method. NSLog statements within the above method (removed) show that the method is being called, that the graphPlotAreaHasFill property is set correctly and the CPTFill is correct. It just isn't updating the plotArea fill. Again, it will only update after rotating the display.
Using Core Plot 1.0.
Any suggestions on how to force the update of the fill area without having to rotate?
Thanks
found the answer here: Calling setNeedsDisplay:YES on layer-hosting view does not redraw the view
turns out because the fill is a CALayer subclass, I can call setNeedsDisplay and it will refresh. Update code is as follows:
-(void)layoutGraphPlotAreaFill{
CPTFill *plotFill=nil;
if (self.graphPlotAreaHasFill) {
plotFill=self.graphPlotAreaFill;
}else{
plotFill=nil;
}
self.graph.plotAreaFrame.plotArea.fill=plotFill;
[self.graph.plotAreaFrame.plotArea setNeedsDisplay];
}

UIView Hell. Hiding one subview hides them all

I am apparently in some swirling UIView hell zone at the moment where up is down sibling is parent and my brain is completely fried.
Here's the deal. Really, really simple. I have a container view with N leaf node sibling subviews. No tricks here, dead simple. I do the following:
// occludedPageSet is the set of view tags corresponding to views that are off screen and // thus fully occluded. This was determined geometrically.
for (NSNumber *n in occludedPageSet) {
// Point to a view corresponding to this tage
UIView *v = [self.containerView viewWithTag:[n integerValue]];
// Hide this view
if (v.hidden == NO) {
NSLog(#"View %d is occluded. Hide it.", [n integerValue]);
v.hidden = YES;
} // if (v.hidden == NO)
} // for (occludedPageSet)
Pretty tame stuff. Unfortunately ALL sibling views vanish! What the?!? How is this possible?
Do I need a [retain]/[release] for v here. I'm stumped.
Baffled,
Doug
Am I missing something about the problem here? It's only natural that if you hide a view, any view it holds as a subview would be hidden as well. After all, you can't see the container view...
If you put ten things in a box and make the box invisible, wouldn't you expect that to mean you couldn't see the things in the box? Similarly an invisibility cloak would be of little use if only the cloak were invisible and not the person beneath...
If you need some things visible and some not, work on the specific items and not the container.
Apparently, all of your views are included in occludedPageSet, or all of your tags are the same n.
NSNumber *n in occludedPageSet
Or, one of the v views is the parent of the rest, so when you hide it, you hide them all.
Make sure self.containerView's tag is something completely different from any of the children's tags. Calling viewWithTag will return the receiver if it is the given tag, which will in turn hide all of your views. Either step through the iteration or print out the address that v points to so that you know you're occluding what you should be occluding.