-[CALayer setNeedsDisplayInRect :]에서 암시 적 애니메이션 비활성화


137

-drawInContext : 메소드에 복잡한 그리기 코드가있는 레이어가 있습니다. 내가해야 할 그림의 양을 최소화하려고 노력 중이므로 변경된 부분 만 업데이트하기 위해 -setNeedsDisplayInRect :를 사용하고 있습니다. 이것은 훌륭하게 작동합니다. 그러나 그래픽 시스템이 레이어를 업데이트하면 크로스 페이드를 사용하여 이전 이미지에서 새 이미지로 전환됩니다. 즉시 전환하고 싶습니다.

CATransaction을 사용하여 작업을 끄고 기간을 0으로 설정했지만 작동하지 않았습니다. 사용중인 코드는 다음과 같습니다.

[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];

CATransaction에 다른 방법이 있습니까? 대신 사용해야합니다 (kCATransactionDisableActions와 같은 -setValue : forKey : 시도).


당신은 다음 실행 루프에서 할 수 있습니다 : dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Hashem Aboonajmi

1
아래에서 많은 답변을 찾았습니다. 또한 내재적 조치 결정 프로세스를 자세히 설명하는 Apple의 레이어 변경 기본 동작 문서 도 유용 합니다.
ɲ euroburɳ

이것은이 하나에 중복 질문 : stackoverflow.com/a/54656717/5067402
라이언 Francesconi

답변:


172

레이어에서 액션 딕셔너리를 설정 [NSNull null]하여 적절한 키에 대한 애니메이션 으로 반환 할 수 있습니다 . 예를 들어

NSDictionary *newActions = @{
    @"onOrderIn": [NSNull null],
    @"onOrderOut": [NSNull null],
    @"sublayers": [NSNull null],
    @"contents": [NSNull null],
    @"bounds": [NSNull null]
};

layer.actions = newActions;

내 레이어 중 하나에서 하위 레이어를 삽입하거나 변경할 때 페이드 인 / 아웃 애니메이션을 비활성화하고 레이어의 크기와 내용을 변경합니다. 나는 믿는다contents업데이트 된 도면에서 크로스 페이드를 방지하기 위해 키가 찾고 열쇠 .


스위프트 버전 :

let newActions = [
        "onOrderIn": NSNull(),
        "onOrderOut": NSNull(),
        "sublayers": NSNull(),
        "contents": NSNull(),
        "bounds": NSNull(),
    ]

24
프레임을 변경할 때 움직임을 방지하려면 @"position"키를 사용하십시오 .
mxcl

11
@"hidden"그런 식으로 레이어의 가시성을 전환하고 불투명도 애니메이션을 비활성화하려는 경우에도 액션 사전에 속성 을 추가해야합니다 .
Andrew

1
@BradLarson 그것은 ,, 및 actionForKey:발견 fontSize, 고군분투 ( 대신 오버 로딩) 후에 생각해 낸 것과 같은 아이디어 입니다. 메소드에 사용할 수있는 키를 지정할 수있는 것처럼 보이며 실제로 복잡한 키 경로를 지정 합니다. contentsonLayoutboundssetValue:forKey:bounds.size
pqnet

11
실제로 속성을 나타내지 않는 이러한 '특수'문자열에 대한 상수가 있습니다 (예 : @ "onOrderOut"에 대한 kCAOnOrderOut) : developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Patrick Pijnappel

1
@Benjohn 해당 속성이없는 키에만 상수가 정의되어 있습니다. BTW, 링크가 죽은 것 같습니다. 여기에 새로운 URL이 있습니다 : developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Patrick Pijnappel

89

또한:

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

//foo

[CATransaction commit];

3
원래 질문에 답변 하기 위해 //foo로 교체 할 수 있습니다 [self setNeedsDisplayInRect: rect]; [self displayIfNeeded];.
Karoy Lorentey 2016 년

1
감사! 이를 통해 사용자 정의보기에서도 애니메이션 플래그를 설정할 수 있습니다. 테이블 뷰 셀 내에서 사용하기에 편리합니다 (셀 재사용으로 스크롤하는 동안 약간의 애니메이션이 발생할 수 있음).
Joe D' Andrea

3
나를위한 성능 문제로
이어지고

26
속기 :[CATransaction setDisableActions:YES]
titaniumdecoy

7
@titaniumdecoy 의견에 추가하는 것은 누군가 나처럼 혼란 스러울 경우를 대비 [CATransaction setDisableActions:YES]하여 [CATransaction setValue:forKey:]줄입니다. 여전히 begincommit줄이 필요합니다 .
Hlung

31

계층의 속성을 변경하면 CA는 일반적으로 변경을 애니메이션하기 위해 암시 적 트랜잭션 개체를 만듭니다. 변경 사항에 애니메이션을 적용하지 않으려면 명시 적 트랜잭션을 만들고 kCATransactionDisableActions 속성을 true로 설정하여 암시 적 애니메이션을 비활성화 할 수 있습니다 .

목표 -C

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
// change properties here without animation
[CATransaction commit];

빠른

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()

6
setDisableActions : 동일하게 수행합니다.
Ben Sinclair

3
이것은 스위프트에서 작업 한 가장 간단한 솔루션이었습니다!
잠바 만

@Andy의 의견은 지금까지 가장 쉽고 쉬운 방법입니다!
Aᴄʜᴇʀᴏɴғᴀɪʟ

23

Brad Larson의 답변 외에도 사용자 정의 레이어 (사용자가 만든 레이어)의 경우 레이어를 수정하는 대신 위임사용할 수 있습니다actions 사전 . 이 방법은보다 역동적이고 성능이 우수 할 수 있습니다. 또한 애니메이션 가능한 모든 키를 나열하지 않고도 모든 암시 적 애니메이션을 비활성화 할 수 있습니다.

불행히도, UIView각각 UIView은 이미 자체 레이어의 대리자 이므로 s를 사용자 정의 레이어 대리자로 사용할 수 없습니다 . 그러나 다음과 같은 간단한 도우미 클래스를 사용할 수 있습니다.

@interface MyLayerDelegate : NSObject
    @property (nonatomic, assign) BOOL disableImplicitAnimations;
@end

@implementation MyLayerDelegate

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    if (self.disableImplicitAnimations)
         return (id)[NSNull null]; // disable all implicit animations
    else return nil; // allow implicit animations

    // you can also test specific key names; for example, to disable bounds animation:
    // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
}

@end

사용법 (보기 내부) :

MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];

// assign to a strong property, because CALayer's "delegate" property is weak
self.myLayerDelegate = delegate;

self.myLayer = [CALayer layer];
self.myLayer.delegate = delegate;

// ...

self.myLayerDelegate.disableImplicitAnimations = YES;
self.myLayer.position = (CGPoint){.x = 10, .y = 42}; // will not animate

// ...

self.myLayerDelegate.disableImplicitAnimations = NO;
self.myLayer.position = (CGPoint){.x = 0, .y = 0}; // will animate

때때로 뷰의 컨트롤러를 뷰의 커스텀 서브 레이어의 델리게이트로 사용하는 것이 편리합니다. 이 경우 도우미 클래스가 필요하지 않으며 actionForLayer:forKey:컨트롤러 내부에서 메서드 를 직접 구현할 수 있습니다 .

중요 사항 : UIView기본 레이어 의 위임을 수정하지 마십시오 (예 : 암시 적 애니메이션 사용). 나쁜 일이 발생합니다.)

참고 : 레이어 다시 그리기에 애니메이션을 적용하지 않으려면 애니메이션을 비활성화하지 않는 경우가 있습니다. 실제 다시 그리기는 때때로 나중에 발생할 수 있기 때문에 [CALayer setNeedsDisplayInRect:]호출 CATransaction할 수 없습니다. 이 답변에 설명 대로 사용자 지정 속성을 사용하는 것이 좋습니다 .


이것은 나를 위해 작동하지 않습니다. 여기를 보아라.
aleclarson

흠. 이 접근법에는 아무런 문제가 없었습니다. 링크 된 질문의 코드는 정상적으로 보이며 문제는 다른 코드로 인한 것일 수 있습니다.
skozin

아, 나는 당신이 이미 작동 CALayer하지 못하게 하는 것이 잘못 되었다는 것을 이미 알았습니다 noImplicitAnimations. 어쩌면 자신의 대답을 올바른 것으로 표시하고 해당 계층에 어떤 문제가 있는지 설명해야합니까?
skozin

나는 단순히 잘못된 CALayer인스턴스로 테스트하고 있었습니다 (당시에 두 개가있었습니다).
aleclarson

1
좋은 해결책 ...하지만 프로토콜을 NSNull구현하지 않으며 CAAction이것은 선택적 방법 만있는 프로토콜이 아닙니다. 이 코드는 충돌이 일어나므로 신속하게 번역 할 수도 없습니다. 더 나은 해결책 : 객체가 CAAction프로토콜을 준수하고 ( runActionForKey:object:arguments:아무 것도 수행하지 않는 빈 메소드 사용) self대신을 반환하십시오 [NSNull null]. 동일한 효과이지만 안전합니다 (확실히 충돌하지는 않음).
Mecki

9

허용 된 답변과 유사하지만 Swift 용보다 효율적인 솔루션이 있습니다. 어떤 경우에는 값을 수정할 때마다 트랜잭션을 생성하는 것보다 성능이 문제가 될 수 있습니다. 예를 들어 60fps에서 레이어 위치를 드래그하는 일반적인 사용 사례가 언급되어 있습니다.

// Disable implicit position animation.
layer.actions = ["position": NSNull()]      

레이어 동작이 해결 되는 방법 은 애플 문서를 참조하십시오 . 대리자를 구현하면 계단식에서 한 단계 더 건너 뛰지 만 대리자는 관련 UIView로 설정 해야하는 것에 대한 경고 로 인해 너무 지저분했습니다 .

편집 :을 NSNull준수 하는 주석 작성자로 인해 업데이트 되었습니다 CAAction.


NullActionSwift 를 만들 필요가 없으며 이미 NSNull준수 CAAction하므로 객관적 C에서와 동일한 작업을 수행 할 수 있습니다. layer.actions = [ "position": NSNull ()]
user5649358

귀하의 답변을이 답변과 결합하여 애니메이션을 적용하기 위해 CATextLayer stackoverflow.com/a/5144221/816017
Erik Zivkovic

이것은 프로젝트에서 CALayer 라인의 색상을 변경할 때 "애니메이션"지연을 우회하는 데 필요한 문제에 대한 훌륭한 수정이었습니다. 감사!!
PlateReverb

짧고 달다! 훌륭한 솔루션!
David H

7

Sam의 답변과 Simon의 어려움을 기반으로 CSShapeLayer를 만든 후 델리게이트 참조를 추가하십시오.

CAShapeLayer *myLayer = [CAShapeLayer layer];
myLayer.delegate = self; // <- set delegate here, it's magic.

"m"파일의 다른 곳에 ...

사용자 정의 "disableImplicitAnimations"변수 배열을 통해 전환 할 수있는 기능이없는 Sam과 기본적으로 동일합니다. "하드 와이어"접근 방식

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {

    // disable all implicit animations
    return (id)[NSNull null];

    // allow implicit animations
    // return nil;

    // you can also test specific key names; for example, to disable bounds animation:
    // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];

}

7

사실, 나는 정답을 찾지 못했습니다. 나를 위해 문제를 해결하는 방법은 다음과 같습니다.

- (id<CAAction>)actionForKey:(NSString *)event {   
    return nil;   
}

그런 다음 논리를 사용하여 특정 애니메이션을 비활성화 할 수 있지만 모든 애니메이션을 제거하고 싶었으므로 nil을 반환했습니다.


5

Swift에서 암시 적 레이어 애니메이션을 비활성화하려면

CATransaction.setDisableActions(true)

이 답변에 감사드립니다. 나는 그것이 똑같은 일 disableActions()을하는 것처럼 들리 면서 처음으로 사용하려고 시도했지만 실제로는 현재 값을 얻는 것입니다. 나는 그것이 표시되어 있다고 생각하기가 @discardable더 어렵다고 생각합니다 . 출처 : developer.apple.com/documentation/quartzcore/catransaction/…
Austin

5

돌며 해제 조치를 간단한 방법을 찾을 CATransaction내부적으로 호출하는 setValue:forKey:에 대한 kCATransactionDisableActions

[CATransaction setDisableActions:YES];

빠른:

CATransaction.setDisableActions(true)

2

이것을 -drawRect () 메소드를 구현하는 사용자 정의 클래스에 추가하십시오. 필요에 맞게 코드를 변경하십시오. '불투명도'는 크로스 페이드 애니메이션을 중지하는 트릭을 만들었습니다.

-(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
{
    NSLog(@"key: %@", key);
    if([key isEqualToString:@"opacity"])
    {
        return (id<CAAction>)[NSNull null];
    }

    return [super actionForLayer:layer forKey:key];
}

1

매우 빠른 (그러나 틀림없이 해키) 수정이 필요한 경우 (Swift)를 수행하는 것이 좋습니다.

let layer = CALayer()

// set other properties
// ...

layer.speed = 999

3
이 ffs를 수행하지 마십시오
m1h4

@ m1h4 감사합니다-이것이 왜 나쁜 생각인지 설명해주세요
Martin CR

3
암시 적 애니메이션을 꺼야하는 경우이를 수행 할 수있는 메커니즘이 있습니다 (임시적으로 비활성화 된 작업이있는 ca 트랜잭션 또는 레이어에 빈 작업을 명시 적으로 설정). 애니메이션 속도를 희망적으로 높은 것으로 설정하면 즉각적으로 보일 정도로 불필요한 성능 오버 헤드 (원래 저자가 언급 한 것과 관련이 있음) 및 다양한 경쟁 조건 (도면이 여전히 별도 버퍼로 수행됨)이 발생할 수 있습니다. 나중에 디스플레이에 애니메이션을 적용하십시오 (위의 경우 0.25 / 999 초 후에는 정확함).
m1h4

view.layer?.actions = [:]실제로 작동하지 않는 것은 부끄러운 일입니다. 속도를 설정하는 것은 추악하지만 작동합니다.
tcurdt

1

MacOS가 아닌 iOS에서 하나의 암시 적 속성 애니메이션 만 신속하고 비활성화하도록 업데이트되었습니다.

// Disable the implicit animation for changes to position
override open class func defaultAction(forKey event: String) -> CAAction? {
    if event == #keyPath(position) {
        return NSNull()
    }
    return super.defaultAction(forKey: event)
}

또 다른 예는이 경우 두 개의 암시 적 애니메이션을 제거하는 것입니다.

class RepairedGradientLayer: CAGradientLayer {

    // Totally ELIMINATE idiotic implicit animations, in this example when
    // we hide or move the gradient layer

    override open class func defaultAction(forKey event: String) -> CAAction? {
        if event == #keyPath(position) {
            return NSNull()
        }
        if event == #keyPath(isHidden) {
            return NSNull()
        }
        return super.defaultAction(forKey: event)
    }
}

0

현재 아이폰 OS 7 단이 작업을 수행하는 편리한 방법이있다 :

[UIView performWithoutAnimation:^{
    // apply changes
}];

1
이 방법이 CALayer 애니메이션을 차단한다고 생각하지 않습니다 .
Benjohn

1
@ Benjohn 아 당신이 옳은 것 같아요. 8 월에 많이 몰랐습니다. 이 답변을 삭제해야합니까?
Warpling

:-) 확실하지 않습니다, 죄송합니다! 주석은 어쨌든 불확실성을 전달하므로 아마 괜찮을 것입니다.
Benjohn

0

CATextLayer의 문자열 속성을 변경할 때 성가신 (흐릿한) 애니메이션을 비활성화하려면 다음을 수행하십시오.

class CANullAction: CAAction {
    private static let CA_ANIMATION_CONTENTS = "contents"

    @objc
    func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
        // Do nothing.
    }
}

그런 다음 그대로 사용하십시오 (예 : 올바른 글꼴 등 CATextLayer를 올바르게 설정하는 것을 잊지 마십시오).

caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]

CATextLayer의 전체 설정은 다음과 같습니다.

private let systemFont16 = UIFont.systemFontOfSize(16.0)

caTextLayer = CATextLayer()
caTextLayer.foregroundColor = UIColor.blackColor().CGColor
caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
caTextLayer.fontSize = systemFont16.pointSize
caTextLayer.alignmentMode = kCAAlignmentCenter
caTextLayer.drawsAsynchronously = false
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
caTextLayer.contentsScale = UIScreen.mainScreen().scale
caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)

uiImageTarget.layer.addSublayer(caTextLayer)
caTextLayer.string = "The text you want to display"

이제 원하는만큼 caTextLayer.string을 업데이트 할 수 있습니다 =)

영감을받은 , 그리고 대답.


0

이 시도.

let layer = CALayer()
layer.delegate = hoo // Same lifecycle UIView instance.

경고

UITableView 인스턴스의 대리자를 설정하면 때때로 충돌이 발생합니다 (아마도 스크롤 뷰의 적중 횟수가 재귀 적으로 호출되었습니다)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.