การปิดใช้งานภาพเคลื่อนไหวโดยนัยใน - [CALayer setNeedsDisplayInRect:]


137

ฉันมีเลเยอร์พร้อมรหัสการวาดที่ซับซ้อนในเมธอด -drawInContext: ฉันพยายามลดจำนวนภาพวาดที่ฉันต้องทำดังนั้นฉันจึงใช้ -setNeedsDisplayInRect: เพื่ออัปเดตเฉพาะส่วนที่เปลี่ยนแปลง มันใช้งานได้อย่างยอดเยี่ยม อย่างไรก็ตามเมื่อระบบกราฟิกอัปเดตเลเยอร์ของฉันมันก็เปลี่ยนจากภาพเก่าเป็นภาพใหม่โดยใช้ cross-fade ฉันต้องการให้สลับทันที

ฉันได้ลองใช้ CATransaction เพื่อปิดการกระทำและตั้งเวลาเป็นศูนย์และไม่ทำงาน นี่คือรหัสที่ฉันใช้:

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

มีวิธีอื่นใน CATransaction ที่ฉันควรใช้แทน (ฉันได้ลอง -setValue: forKey: with kCATransactionDisableActions, ผลลัพธ์เดียวกัน)


คุณสามารถทำได้ในการวนรอบถัดไป: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Hashem Aboonajmi

1
ฉันพบคำตอบมากมายสำหรับฉันที่ด้านล่าง ยังมีประโยชน์อีกอย่างคือเอกสารการเปลี่ยนพฤติกรรมเริ่มต้นของเลเยอร์ของ Apple ซึ่งจะอธิบายขั้นตอนการตัดสินใจดำเนินการโดยปริยายในรายละเอียด
uroeuroburɳ

นี่เป็นคำถามที่ซ้ำกันสำหรับคำถามนี้: stackoverflow.com/a/54656717/5067402
Ryan Francesconi

คำตอบ:


172

คุณสามารถทำได้โดยการตั้งค่าพจนานุกรมการกระทำบนเลเยอร์เพื่อกลับมา[NSNull null]เป็นภาพเคลื่อนไหวสำหรับคีย์ที่เหมาะสม ตัวอย่างเช่นฉันใช้

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

layer.actions = newActions;

เพื่อปิดการใช้งานจางหายไปของภาพเคลื่อนไหวเข้า / ออกในการแทรกหรือเปลี่ยน sublayers ภายในเลเยอร์ของฉันเช่นเดียวกับการเปลี่ยนแปลงขนาดและเนื้อหาของเลเยอร์ ฉันเชื่อว่าcontentsกุญแจสำคัญคือสิ่งที่คุณกำลังมองหาเพื่อป้องกันไม่ให้ crossfade ในรูปวาดที่อัปเดต


รุ่น Swift:

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

24
เพื่อป้องกันการเคลื่อนไหวเมื่อเปลี่ยนเฟรมให้ใช้@"position"กุญแจ
mxcl

11
ตรวจสอบให้แน่ใจว่าได้เพิ่ม@"hidden"คุณสมบัติในพจนานุกรมการกระทำด้วยหากคุณสลับการแสดงเลเยอร์ในแบบนั้นและต้องการปิดใช้งานภาพเคลื่อนไหวทึบ
แอนดรู

1
@BradLarson ที่ความคิดเดียวกันฉันมาด้วยหลังจากที่บางดิ้นรน (i ลบล้างactionForKey:แทน), การค้นพบfontSize, contents, และonLayout boundsดูเหมือนว่าคุณสามารถระบุคีย์ใด ๆ ที่คุณสามารถใช้ในsetValue:forKey:วิธีการระบุเส้นทางคีย์ที่ซับซ้อนเช่นbounds.sizeจริง ๆ
pqnet

11
มีค่าคงที่สำหรับสตริง 'พิเศษ' เหล่านี้ที่ไม่ได้แสดงถึงคุณสมบัติ (เช่น kCAOnOrderOut สำหรับ @ "onOrderOut") ที่มีเอกสาร
ครบถ้วน

1
@Benjohn เฉพาะคีย์ที่ไม่มีคุณสมบัติที่สอดคล้องกันเท่านั้นที่มีค่าคงที่ที่กำหนดไว้ BTW ลิงก์ดูเหมือนจะตายนี่คือ URL ใหม่: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/
......

89

นอกจากนี้:

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

//foo

[CATransaction commit];

3
คุณสามารถแทนที่//fooด้วย[self setNeedsDisplayInRect: rect]; [self displayIfNeeded];เพื่อตอบคำถามเดิม
Karoy Lorentey

1
ขอบคุณ! สิ่งนี้ช่วยให้ฉันตั้งค่าสถานะภาพเคลื่อนไหวบนมุมมองที่กำหนดเองของฉันได้เช่นกัน มีประโยชน์สำหรับการใช้งานภายในเซลล์มุมมองตาราง (ซึ่งการใช้ซ้ำเซลล์สามารถนำไปสู่การเคลื่อนไหวของ Trippy ในขณะเลื่อน)
Joe D'Andrea

3
นำไปสู่ปัญหาด้านประสิทธิภาพสำหรับฉันการตั้งค่าการกระทำนั้นมีประสิทธิภาพมากขึ้น
Pascalius

26
จดชวเลข:[CATransaction setDisableActions:YES]
titaniumdecoy

7
การเพิ่มความคิดเห็น @titaniumdecoy ในกรณีที่มีใครสับสน (เช่นฉัน) [CATransaction setDisableActions:YES]เป็นชวเลขเพียง[CATransaction setValue:forKey:]บรรทัด คุณยังต้องการbeginและcommitเส้น
Hlung

31

เมื่อคุณเปลี่ยนคุณสมบัติของเลเยอร์โดยปกติแล้ว CA จะสร้างวัตถุธุรกรรมโดยนัยเพื่อให้เกิดการเปลี่ยนแปลง หากคุณไม่ต้องการให้เคลื่อนไหวเปลี่ยนแปลงคุณสามารถปิดการใช้ภาพเคลื่อนไหวโดยปริยายโดยการสร้างการทำธุรกรรมอย่างชัดเจนและการตั้งค่าของkCATransactionDisableActionsคุณสมบัติการจริง

Objective-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: ทำเช่นเดียวกัน
เบ็นซินแคลร์

3
อันนี้เป็นวิธีการแก้ปัญหาที่ง่ายที่สุดที่ฉันได้ทำงานใน Swift!
Jambaman

ความคิดเห็นโดย @Andy เป็นวิธีที่ดีที่สุดและง่ายที่สุดในการทำเช่นนี้!
A

23

นอกเหนือจากคำตอบของ Brad Larson : สำหรับเลเยอร์ที่กำหนดเอง (ที่คุณสร้างขึ้น) คุณสามารถใช้การมอบหมายแทนการปรับเปลี่ยนเลเยอร์ของactionsพจนานุกรม วิธีการนี้เป็นแบบไดนามิกมากขึ้นและอาจมีประสิทธิภาพมากขึ้น และอนุญาตให้ปิดใช้งานภาพเคลื่อนไหวโดยนัยทั้งหมดโดยไม่ต้องแสดงรายการคีย์ที่สามารถเคลื่อนไหวได้ทั้งหมด

น่าเสียดายที่มันเป็นไปไม่ได้ที่จะใช้UIViews เป็นตัวแทนเลเยอร์ที่กำหนดเองเพราะแต่ละคนUIViewเป็นตัวแทนของเลเยอร์ของตัวเองอยู่แล้ว แต่คุณสามารถใช้คลาสตัวช่วยอย่างนี้:

@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

บางครั้งมันก็สะดวกที่จะมีตัวควบคุมของมุมมองในฐานะตัวแทนสำหรับ sublayers ที่กำหนดเองของมุมมอง; ในกรณีนี้ไม่จำเป็นต้องมีคลาสผู้ช่วยคุณสามารถใช้actionForLayer:forKey:วิธีการภายในคอนโทรลเลอร์

หมายเหตุสำคัญ: อย่าพยายามแก้ไขผู้รับมอบสิทธิ์ของUIViewเลเยอร์ต้นแบบ (เช่นเพื่อเปิดใช้งานภาพเคลื่อนไหวโดยนัย) - สิ่งเลวร้ายจะเกิดขึ้น :)

หมายเหตุ: หากคุณต้องการเคลื่อนไหวเลเยอร์การวาดใหม่ (ไม่ปิดใช้งานภาพเคลื่อนไหวสำหรับ) จะไม่มีประโยชน์ในการวาง[CALayer setNeedsDisplayInRect:]สายในCATransactionเพราะการวาดภาพจริงอาจเกิดขึ้นได้ในบางครั้งในภายหลัง แนวทางที่ดีคือการใช้คุณสมบัติที่กำหนดเองดังที่อธิบายไว้ในคำตอบนี้


มันไม่ทำงานสำหรับฉัน ดูที่นี่
aleclarson

อืมม ฉันไม่เคยมีปัญหาใด ๆ กับวิธีการนี้ รหัสในคำถามที่เชื่อมโยงนั้นดูดีและอาจเป็นปัญหาที่เกิดจากรหัสอื่น ๆ
skozin

อาฉันเห็นว่าคุณแยกออกแล้วว่ามันผิดCALayerที่ป้องกันไม่ให้noImplicitAnimationsทำงาน บางทีคุณควรทำเครื่องหมายคำตอบของคุณเองให้ถูกต้องและอธิบายสิ่งที่ผิดกับเลเยอร์นั้น?
skozin

ฉันแค่ทดสอบกับCALayerอินสแตนซ์ที่ผิด(ฉันมีอยู่สองอันในเวลานั้น)
aleclarson

1
วิธีแก้ปัญหาที่ดี ... แต่NSNullไม่ได้ใช้CAActionโปรโตคอลและนี่ไม่ใช่โปรโตคอลที่มีวิธีการเสริมเท่านั้น รหัสนี้ผิดพลาดเช่นกันและคุณไม่สามารถแปลได้อย่างรวดเร็ว ทางออกที่ดีกว่า: ตรวจวัตถุของคุณเป็นไปตามCAActionโปรโตคอล (มีที่ว่างเปล่าrunActionForKey:object:arguments:วิธีการที่ไม่ทำอะไรเลย) และกลับแทนself [NSNull null]ผลเหมือนกัน แต่ปลอดภัย (จะไม่ผิดพลาดอย่างแน่นอน) และยังใช้งานได้ใน Swift
Mecki

9

นี่เป็นทางออกที่มีประสิทธิภาพมากขึ้นคล้ายกับคำตอบที่ได้รับการยอมรับ แต่สำหรับสวิฟท์ สำหรับบางกรณีมันจะดีกว่าการสร้างธุรกรรมทุกครั้งที่คุณแก้ไขค่าซึ่งเป็นข้อกังวลเกี่ยวกับประสิทธิภาพเช่นเดียวกับกรณีอื่น ๆ เช่นกรณีการใช้งานทั่วไปของการลากตำแหน่งเลเยอร์ที่ 60fps

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

ดูเอกสารของ Apple สำหรับวิธีการดำเนินการชั้นได้รับการแก้ไข การดำเนินการของผู้ร่วมประชุมจะข้ามระดับหนึ่งในน้ำตก แต่ในกรณีของฉันที่เป็นยุ่งเกินไปเนื่องจากการข้อแม้เกี่ยวกับการมอบหมายที่จะต้องกำหนดให้ UIView

แก้ไข: Updated ขอบคุณที่แสดงความคิดเห็นชี้ให้เห็นว่าสอดรับไปNSNullCAAction


ไม่จำเป็นต้องสร้างNullActionสำหรับ Swift เป็นNSNullไปตามที่CAActionมีอยู่แล้วเพื่อให้คุณสามารถทำเช่นเดียวกับที่คุณทำในวัตถุประสงค์ C: layer.actions = ["ตำแหน่ง": NSNull ()]
user5649358

ฉันรวมคำตอบของคุณกับอันนี้เพื่อแก้ไขการเคลื่อนไหวของฉัน CATextLayer stackoverflow.com/a/5144221/816017
Erik Zivkovic

นี่เป็นการแก้ไขที่ยอดเยี่ยมสำหรับปัญหาของฉันที่ต้องการหลีกเลี่ยงความล่าช้า "แอนิเมชัน" เมื่อเปลี่ยนสีของบรรทัด CALayer ในโครงการของฉัน ขอบคุณ !!
PlateReverb

สั้นและหวาน! สุดยอดทางออก!
David H

7

ตามคำตอบของแซมและปัญหาของ Simon ... เพิ่มการอ้างอิงผู้มอบหมายหลังจากสร้าง CSShapeLayer:

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

... ที่อื่นในไฟล์ "m" ...

เป็นหลักเช่นเดียวกับแซมโดยไม่มีความสามารถในการสลับผ่านการจัดเรียงตัวแปรที่กำหนดเอง "disableImplicitAnimations" มีวิธี "ลวดแข็ง" มากขึ้น

- (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;   
}

จากนั้นคุณสามารถใช้เหตุผลใด ๆ ในนั้นเพื่อปิดการใช้งานภาพเคลื่อนไหวที่เฉพาะเจาะจง แต่เนื่องจากฉันต้องการที่จะลบพวกเขาทั้งหมดฉันกลับไม่มีศูนย์


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
โปรดอย่าทำสิ่งนี้เลย
m1h4

@ m1h4 ขอบคุณสำหรับสิ่งนั้น - โปรดอธิบายว่าทำไมนี่เป็นความคิดที่ไม่ดี
Martin CR

3
เพราะหากจำเป็นต้องปิดการเคลื่อนไหวโดยนัยมีกลไกสำหรับการทำเช่นนั้น (ทั้งธุรกรรม ca ด้วยการกระทำที่ปิดการใช้งานชั่วคราวหรือการตั้งค่าการกระทำที่ว่างเปล่าลงบนเลเยอร์อย่างชัดเจน) เพียงแค่ตั้งค่าความเร็วของภาพเคลื่อนไหวเป็นสิ่งที่หวังว่าจะสูงพอที่จะทำให้มันเกิดขึ้นทันทีทันใดทำให้เกิดภาระค่าใช้จ่ายที่ไม่จำเป็น (ซึ่งผู้เขียนคนแรกกล่าวถึงเขา) และศักยภาพสำหรับสภาพการแข่งขันต่าง ๆ นำภาพเคลื่อนไหวเข้าสู่จอแสดงผลในภายหลังเพื่อความแม่นยำสำหรับกรณีของคุณด้านบนที่ 0.25 / 999 วินาทีในภายหลัง)
m1h4

มันเป็นความอัปยศที่view.layer?.actions = [:]ไม่ได้ผลจริงๆ การตั้งค่าความเร็วน่าเกลียด แต่ใช้งานได้
tcurdt

1

อัปเดตเพื่อความรวดเร็วและปิดใช้งานภาพเคลื่อนไหวของคุณสมบัติโดยนัยเพียงรายการเดียวใน iOS ไม่ใช่ MacOS

// 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

ตั้งแต่iOS 7มีวิธีอำนวยความสะดวกที่ทำได้ดังนี้:

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

1
ผมไม่เชื่อว่าวิธีนี้บล็อกภาพเคลื่อนไหว CALayer
Benjohn

1
@Benjohn Ah ฉันคิดว่าคุณพูดถูก ไม่ทราบมากในเดือนสิงหาคม ฉันควรลบคำตอบนี้หรือไม่
สุ่มตัวอย่าง

:-) ฉันไม่แน่ใจเหมือนกันขอโทษ! ความคิดเห็นสื่อสารถึงความไม่แน่นอนอยู่แล้วดังนั้นจึงอาจไม่เป็นไร
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 บางครั้งอาจเกิดข้อผิดพลาด (อาจเป็นเพราะ hittest ของ scrollview ที่เรียกซ้ำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.