Vô hiệu hóa hình ảnh động ngầm trong - [CALayer setNeedsDisplayInRect:]


137

Tôi đã có một lớp với một số mã bản vẽ phức tạp trong phương thức -drawInContext :. Tôi đang cố gắng giảm thiểu số lượng bản vẽ cần thực hiện, vì vậy tôi đang sử dụng -setNeedsDisplayInRect: để cập nhật chỉ các phần đã thay đổi. Điều này đang làm việc tuyệt vời. Tuy nhiên, khi hệ thống đồ họa cập nhật lớp của tôi, nó sẽ chuyển từ hình ảnh cũ sang hình ảnh mới bằng cách sử dụng mờ dần. Tôi muốn nó chuyển qua ngay lập tức.

Tôi đã thử sử dụng CATransaction để tắt các hành động và đặt thời lượng thành 0 và không hoạt động. Đây là mã tôi đang sử dụng:

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

Có một phương pháp khác trên CATransaction tôi nên sử dụng thay thế (tôi cũng đã thử -setValue: forKey: với kCATransactionDisableActions, kết quả tương tự).


bạn có thể làm điều đó trong vòng chạy tiếp theo: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
Hashem Aboonajmi

1
Tôi tìm thấy nhiều câu trả lời dưới đây để làm việc cho tôi. Cũng hữu ích là Apple thay đổi tài liệu Hành vi mặc định của Layer , mô tả chi tiết quá trình ra quyết định hành động.
ɲeuroburɳ

Đây là một câu hỏi trùng lặp với câu hỏi này: stackoverflow.com/a/54656717/5067402
Ryan Francesconi

Câu trả lời:


172

Bạn có thể làm điều này bằng cách đặt từ điển hành động trên lớp để trở lại [NSNull null]làm hình động cho khóa thích hợp. Ví dụ, tôi sử dụng

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

layer.actions = newActions;

để vô hiệu hóa hiệu ứng mờ dần trong / ngoài khi chèn hoặc thay đổi lớp con trong một trong các lớp của tôi, cũng như thay đổi kích thước và nội dung của lớp. Tôi tin rằng contentschìa khóa là thứ bạn đang tìm kiếm để ngăn chặn sự xen kẽ trên bản vẽ được cập nhật.


Phiên bản Swift:

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

24
Để ngăn chuyển động khi thay đổi khung, hãy sử dụng @"position"phím.
mxcl

11
Ngoài ra, hãy chắc chắn để thêm thuộc @"hidden"tính trong từ điển hành động nếu bạn đang chuyển đổi khả năng hiển thị của một lớp theo cách đó và muốn tắt hoạt hình mờ.
Andrew

1
@BradLarson đó là ý tưởng cùng tôi đã đưa ra sau khi một số đang gặp khó khăn (i overrode actionForKey:thay), khám phá fontSize, contents, onLayoutbounds. Có vẻ như bạn có thể chỉ định bất kỳ khóa nào bạn có thể sử dụng trong setValue:forKey:phương thức, thực sự chỉ định các đường dẫn khóa phức tạp như thế nào bounds.size.
pqnet

11
Thực tế, có hằng số cho những chuỗi 'đặc biệt' không đại diện cho một tài sản (ví dụ kCAOnOrderOut cho @ "onOrderOut") cũng như các tài liệu ở đây: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/...
Patrick Pijnappel

1
@Benjohn Chỉ các khóa không có thuộc tính tương ứng mới được xác định. BTW, liên kết có vẻ là đã chết, đây là URL mới: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
Patrick Pijnappel

89

Cũng thế:

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

//foo

[CATransaction commit];

3
Bạn có thể thay thế //foobằng [self setNeedsDisplayInRect: rect]; [self displayIfNeeded];để trả lời câu hỏi ban đầu.
Karoy Lorentey

1
Cảm ơn! Điều này cho phép tôi đặt cờ hoạt hình trên chế độ xem tùy chỉnh của mình. Tiện dụng để sử dụng trong một ô xem bảng (trong đó việc sử dụng lại ô có thể dẫn đến một số hình ảnh động nhanh trong khi cuộn).
Joe D'Andrea

3
Dẫn đến các vấn đề về hiệu suất đối với tôi, thiết lập các hành động hiệu quả hơn
Pascalius

26
Tốc ký:[CATransaction setDisableActions:YES]
titandecoy

7
Thêm vào bình luận @titaniumdecoy, chỉ trong trường hợp bất kỳ ai bị nhầm lẫn (như tôi), [CATransaction setDisableActions:YES]là một cách viết tắt cho chỉ [CATransaction setValue:forKey:]dòng. Bạn vẫn cần các dòng begincommit.
Hlung

31

Khi bạn thay đổi thuộc tính của một lớp, CA thường tạo một đối tượng giao dịch ngầm để tạo hiệu ứng thay đổi. Nếu bạn không muốn làm động các thay đổi, bạn có thể vô hiệu hóa hoạt ảnh ngầm bằng cách tạo một giao dịch rõ ràng và đặt thuộc tính kCATransactionDisableActions của nó thành đúng .

Mục tiêu-C

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

Nhanh

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

6
setDisableActions: thực hiện tương tự.
Ben Sinclair

3
Đây là giải pháp đơn giản nhất tôi có được khi làm việc trong Swift!
Jambaman

Nhận xét của @Andy là cách tốt nhất và dễ nhất để làm điều này!
Aᴄʜᴇʀᴏɴғᴀɪʟ

23

Ngoài câu trả lời của Brad Larson : đối với các lớp tùy chỉnh (do bạn tạo), bạn có thể sử dụng ủy quyền thay vì sửa đổi lớpactions từ điển . Cách tiếp cận này năng động hơn và có thể hiệu quả hơn. Và nó cho phép vô hiệu hóa tất cả các hình ảnh động ngầm mà không phải liệt kê tất cả các phím có thể hoạt hình.

Thật không may, không thể sử dụng UIViews làm đại biểu lớp tùy chỉnh, vì mỗi người UIViewđã là đại biểu của lớp riêng. Nhưng bạn có thể sử dụng một lớp trợ giúp đơn giản như thế này:

@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

Cách sử dụng (bên trong khung nhìn):

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

Đôi khi, thật tiện lợi khi có bộ điều khiển của view làm đại biểu cho các lớp con tùy chỉnh của view; trong trường hợp này không cần lớp trợ giúp, bạn có thể thực hiện actionForLayer:forKey:phương thức ngay bên trong bộ điều khiển.

Lưu ý quan trọng: đừng cố gắng sửa đổi ủy nhiệm của UIViewlớp bên dưới (ví dụ: để kích hoạt hoạt ảnh ngầm) - điều tồi tệ sẽ xảy ra :)

Lưu ý: nếu bạn muốn tạo hiệu ứng (không vô hiệu hóa hoạt hình cho) vẽ lại lớp, việc đặt [CALayer setNeedsDisplayInRect:]cuộc gọi vào bên trong là vô ích CATransaction, bởi vì việc vẽ lại thực tế có thể (và có thể sẽ xảy ra sau đó). Cách tiếp cận tốt là sử dụng các thuộc tính tùy chỉnh, như được mô tả trong câu trả lời này .


Điều này không làm việc cho tôi. Xem ở đây.
aleclarson

Hừm. Tôi chưa bao giờ có bất kỳ vấn đề với phương pháp này. Mã trong câu hỏi được liên kết có vẻ ổn và có thể vấn đề là do một số mã khác gây ra.
skozin

Ah, tôi thấy rằng bạn đã sắp xếp ra rằng nó sai CALayerkhiến nó không hoạt noImplicitAnimationsđộng. Có lẽ bạn nên đánh dấu câu trả lời của riêng mình là chính xác và giải thích điều gì sai với lớp đó?
skozin

Tôi chỉ đơn giản là thử nghiệm với CALayertrường hợp sai (lúc đó tôi có hai cái).
aleclarson

1
Giải pháp tuyệt vời ... nhưng NSNullkhông thực hiện CAActiongiao thức và đây không phải là giao thức chỉ có các phương thức tùy chọn. Mã này cũng bị sập và bạn thậm chí không thể dịch nó sang nhanh. Giải pháp tốt hơn: Làm cho đối tượng của bạn tuân thủ CAActiongiao thức (với một runActionForKey:object:arguments:phương thức trống không có gì) và trả về selfthay vì [NSNull null]. Hiệu ứng tương tự nhưng an toàn (chắc chắn sẽ không sụp đổ) và cũng hoạt động trong Swift.
Mecki

9

Đây là một giải pháp hiệu quả hơn, tương tự như câu trả lời được chấp nhận nhưng dành cho Swift . Đối với một số trường hợp, sẽ tốt hơn là tạo giao dịch mỗi khi bạn sửa đổi giá trị là mối quan tâm về hiệu suất như những người khác đã đề cập, ví dụ như trường hợp sử dụng phổ biến là kéo vị trí lớp khoảng 60fps.

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

Xem tài liệu của apple để biết cách giải quyết các hành động của lớp . Việc triển khai đại biểu sẽ bỏ qua một cấp độ nữa trong bậc thang nhưng trong trường hợp của tôi quá lộn xộn do sự cảnh báo về đại biểu cần phải được đặt thành UIView liên quan .

Chỉnh sửa: Cập nhật nhờ người bình luận chỉ ra rằng NSNullphù hợp với CAAction.


Không cần tạo NullActionSwift, NSNulltuân thủ để CAActionbạn có thể thực hiện tương tự như bạn đã làm trong mục tiêu C: layer.ilities = ["location": NSNull ()]
user5649353 7/12/2015

Tôi đã kết hợp câu trả lời của bạn với câu hỏi này để sửa lỗi stackoverflow
Erik Zivkovic

Đây là một sửa chữa tuyệt vời cho vấn đề cần thiết của tôi để vượt qua độ trễ "hoạt hình" khi thay đổi màu của các dòng CALayer trong dự án của tôi. Cảm ơn!!
MảngReverb

Ngắn và ngọt! Giải pháp tuyệt vời!
David H

7

Dựa trên câu trả lời của Sam và những khó khăn của Simon ... thêm tham chiếu của đại biểu sau khi tạo CSShapeLayer:

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

... ở nơi khác trong tệp "m" ...

Về cơ bản giống như Sam mà không có khả năng chuyển đổi thông qua cách sắp xếp biến "vô hiệu hóa tùy chỉnh". Thêm một cách tiếp cận "cứng cáp".

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

Trên thực tế, tôi đã không tìm thấy bất kỳ câu trả lời nào là đúng. Phương pháp giải quyết vấn đề cho tôi là:

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

Sau đó, bạn có thể bất cứ logic nào trong đó, để vô hiệu hóa một hình ảnh động cụ thể, nhưng vì tôi muốn loại bỏ tất cả chúng, tôi trở lại con số không.


5

Để vô hiệu hóa hoạt ảnh lớp ẩn trong Swift

CATransaction.setDisableActions(true)

Cảm ơn câu trả lời này. Lần đầu tiên tôi thử sử dụng disableActions()vì có vẻ như nó cũng làm điều tương tự, nhưng thực ra nó để có được giá trị hiện tại. Tôi nghĩ rằng nó cũng được đánh dấu @discardable, làm cho điều này khó phát hiện hơn. Nguồn: developer.apple.com/documentation/quartzcore/catransaction/
Austin

5

Tìm ra một phương pháp đơn giản hơn để vô hiệu hóa hành động bên trong một CATransactioncuộc gọi nội bộ setValue:forKey:cho kCATransactionDisableActionskhóa:

[CATransaction setDisableActions:YES];

Nhanh:

CATransaction.setDisableActions(true)

2

Thêm phần này vào lớp tùy chỉnh của bạn nơi bạn đang thực hiện phương thức -drawRect (). Thay đổi mã để phù hợp với nhu cầu của bạn, đối với tôi, 'độ mờ đục' đã thực hiện thủ thuật ngăn chặn hoạt ảnh mờ dần.

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

Nếu bạn cần một bản sửa lỗi rất nhanh (nhưng phải thừa nhận), có thể chỉ cần thực hiện (Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999

3
Xin đừng bao giờ làm điều này ffs
m1h4

@ m1h4 cảm ơn vì điều đó - vui lòng giải thích lý do tại sao đây là một ý tưởng tồi
Martin CR

3
Bởi vì nếu người ta cần tắt hoạt hình ngầm, có một cơ chế để thực hiện điều đó (có thể là một giao dịch ca với các hành động bị vô hiệu hóa tạm thời hoặc đặt rõ ràng các hành động trống lên một lớp). Chỉ cần đặt tốc độ hoạt hình ở mức đủ cao để khiến nó dường như ngay lập tức gây ra vô số chi phí hiệu suất không cần thiết (mà tác giả ban đầu đề cập có liên quan đến anh ta) và tiềm năng cho các điều kiện chủng tộc khác nhau (bản vẽ vẫn được thực hiện trong một bộ đệm riêng biệt được hoạt hình vào màn hình ở một điểm sau - chính xác hơn, đối với trường hợp của bạn ở trên, ở mức 0,25 / 999 giây sau).
m1h4

Thật sự là một sự xấu hổ view.layer?.actions = [:] không thực sự làm việc. Đặt tốc độ là xấu nhưng hoạt động.
tcurdt

1

Được cập nhật cho swift và chỉ vô hiệu hóa một hình động thuộc tính ẩn trong iOS không phải là 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)
}

Một ví dụ khác, trong trường hợp này loại bỏ hai hình ảnh động ngầm.

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

Kể từ iOS 7, có một phương pháp tiện lợi thực hiện điều này:

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

1
Tôi không tin rằng phương pháp này chặn hoạt ảnh CALayer .
Benjohn

1
@Benjohn Ah tôi nghĩ bạn đúng. Không biết nhiều vào tháng Tám. Tôi có nên xóa câu trả lời này?
Warpling

:-) Tôi cũng không bao giờ chắc chắn, xin lỗi! Các ý kiến ​​truyền đạt sự không chắc chắn dù sao, vì vậy nó có thể ổn.
Stewohn 22/2/2016

0

Để vô hiệu hóa hình động gây khó chịu (mờ) khi thay đổi thuộc tính chuỗi của CATextLayer, bạn có thể làm điều này:

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

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

và sau đó sử dụng nó như vậy (đừng quên thiết lập CATextLayer của bạn đúng cách, ví dụ: phông chữ chính xác, v.v.):

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

Bạn có thể thấy thiết lập hoàn chỉnh của tôi về CATextLayer tại đây:

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"

Bây giờ bạn có thể cập nhật caTextLayer.opes bao nhiêu tùy ý =)

Lấy cảm hứng từ điều này , và câu trả lời này .


0

Thử cái này.

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

Cảnh báo

Nếu bạn đặt đại biểu của phiên bản UITableView, đôi khi xảy ra sự cố. (Có lẽ là đáng tiếc nhất của scrollview được gọi là đệ quy.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.