2017-05-12 3 views
0

beginUpdatesendUpdates 메서드를 사용하여 확장하고 축소하려고 시도하는 UITableView가 있으며 그 때 그림자가 표시됩니다.CALayer 그림자를 동시에 UITableviewCell 높이 애니메이션으로 적용합니다.

self.shadowLayer.shadowColor = self.shadowColor.CGColor; 
self.shadowLayer.shadowOffset = CGSizeMake(self.shadowOffsetWidth, self.shadowOffsetHeight); 
self.shadowLayer.shadowOpacity = self.shadowOpacity; 
self.shadowLayer.masksToBounds = NO; 
self.shadowLayer.frame = self.layer.bounds; 
// this is extremely important for performance when drawing shadows 
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.shadowLayer.frame cornerRadius:self.cornerRadius]; 
self.shadowLayer.shadowPath = shadowPath.CGPath; 

내가 viewDidLoad에있는 UITableViewCell이 레이어를 추가 : 내 사용자 지정있는 UITableViewCell에, 나는에 layoutSubviews에 대한 그림자를 만들 층이

내가 알고있는 것처럼
self.shadowLayer = [CALayer layer]; 
self.shadowLayer.backgroundColor = [UIColor whiteColor].CGColor; 
[self.layer insertSublayer:self.shadowLayer below:self.contentView.layer]; 

, 내가 전화 beginUpdates이면 현재 실행 루프가 없으면 암시 적 CALayerTransaction이 실행됩니다. 또한 layoutSubviews도 호출됩니다. 여기서 문제는 UITableViewCell의 새 크기를 기반으로 결과 그림자가 즉시 그려진다는 점입니다. 실제 레이어가 움직이는 것처럼 계속 예상되는 방식으로 캐스팅을 계속하려면 그림자가 필요합니다.

내가 만든 레이어는 백업 CALayer가 아니기 때문에 Catransaction을 명시 적으로 지정하지 않아도 애니메이션이 재생됩니다. 그러나, 나는 그것을 이해하기 때문에, 실제로 CATransaction beginUpdates/endUpdates의 잡아 잡고 그 안에서 애니메이션을 수행 할 수있는 방법이 필요합니다. 그렇다면 어떻게해야합니까?

+0

그림자가 하나의 셀 주위에 있거나 전체 테이블 주위에 있어야합니까? –

답변

0

UITableViewCATransaction을 생성하지 않을 가능성이 있으며 업데이트가 끝날 때까지 기다리는 중입니다. 테이블 뷰는 이러한 함수들 간의 모든 변경 사항을 합친 다음 필요에 따라 애니메이션을 생성합니다. 실제로 발생하는시기를 알 수 없기 때문에 커밋중인 실제 애니메이션 매개 변수를 처리 할 수있는 방법이 없습니다. UIScrollView의 내용 오프셋 변경을 애니메이션으로 적용 할 때도 마찬가지입니다. 시스템은 애니메이션 자체에 대한 컨텍스트를 제공하지 않아 실망합니다. 또한 시스템에 현재 CATransaction을 쿼리 할 수있는 방법이 없습니다.

아마도 여러분이 할 수있는 가장 좋은 방법은 UITableView이 생성하는 애니메이션을 검사하고 자신의 애니메이션에서 동일한 타이밍 매개 변수를 모방하는 것입니다. add(_:forKey:)CALayer에 올리면 추가되는 모든 애니메이션을 검사 할 수 있습니다. 확실히이 기능을 실제로 제공하고 싶지는 않지만, 디버깅에서이 기술을 사용하여 추가되는 애니메이션과 해당 속성을 확인합니다.

적절한 셀에 대해 자신의 그림자 레이어 애니메이션을 tableView(_:willDisplayCell:for:row:)에 커밋해야한다고 생각합니다. 그래서

5

난 당신이 이런 식으로 뭔가있는 것 같아요 :

bad animation

가 (. 내가 시뮬레이터에서 "디버그> 느린 애니메이션"켜져)를 그리고 당신은 그림자로 이동하는 방식을 좋아하지 않아 그 새로운 크기. 이 대신 원하는 :

good animation

당신은 내 테스트 프로젝트 in this github repository를 찾을 수 있습니다.

애니메이션 매개 변수를 선택하고 표보기의 애니메이션 블록에 애니메이션을 추가하는 것은 까다롭지 만 불가능하지는 않습니다. 가장 까다로운 부분은 음영 처리 된 뷰 자체 또는 음영 처리 된 뷰의 즉각적인 수퍼 뷰의 layoutSubviews 메서드에서 shadowPath을 업데이트해야한다는 것입니다.내 데모 비디오에서는 을 layoutSubviews 녹색 상자보기 또는 녹색 상자의 즉각적인 수퍼 뷰 방법으로 업데이트해야한다는 것을 의미합니다.

나는 그의 유일한 직업 그릴과 파단 중 하나의 그림자 애니메이션을하는 것입니다 ShadowingView 클래스를 작성하기로 결정했습니다.

@interface ShadowingView : UIView 

@property (nonatomic, strong) IBOutlet UIView *shadowedView; 

@end 

가 내 스토리 보드에서 내 휴대보기에 추가, ShadowingView을 사용하려면 다음은 인터페이스입니다. 사실 그것은 셀 내부의 스택보기 안에 중첩되어 있습니다. 그런 다음 녹색 상자를 ShadowingView의 하위보기로 추가하고 콘센트를 녹색 상자에 shadowedView 연결했습니다.

ShadowingView 구현은 세 부분으로 구성됩니다.

경우와 같이이 방법 (애니메이션 블록 내에서 실행된다
@implementation ShadowingView 

- (void)layoutSubviews { 
    [super layoutSubviews]; 

    CALayer *layer = self.layer; 
    layer.backgroundColor = nil; 

    CALayer *shadowedLayer = self.shadowedView.layer; 
    if (shadowedLayer == nil) { 
     layer.shadowColor = nil; 
     return; 
    } 

    NSAssert(shadowedLayer.superlayer == layer, @"shadowedView must be my direct subview"); 

    layer.shadowColor = UIColor.blackColor.CGColor; 
    layer.shadowOffset = CGSizeMake(0, 1); 
    layer.shadowOpacity = 0.5; 
    layer.shadowRadius = 3; 
    layer.masksToBounds = NO; 

    CGFloat radius = shadowedLayer.cornerRadius; 
    layer.shadowPath = CGPathCreateWithRoundedRect(shadowedLayer.frame, radius, radius, nil); 
} 

테이블 : 하나는 자신의 층 상에 층 그림자 속성 설정의 layoutSubviews있어서, 그 shadowedView 하위 뷰 주위 그림자를 그리는 것이다 보기) 셀의 크기의 변화를 애니메이션 및 방법은 "액션이"shadowPath를 업데이트 한 후 실행하는 shadowPath는, 코어 애니메이션 보이는 설정합니다. 보이는 방법 중 하나는 actionForLayer:forKey:을 레이어의 위임에 보내고 위임은 ShadowingView입니다. 가능한 경우 적절한 조치를 취하기 위해 actionForLayer:forKey:을 무시합니다. 할 수 없다면 super으로 전화하면됩니다.

코어 애니메이션은 실제로 shadowPath의 값을 변경 전에 shadowPath 세터 내부에서 작업을 요청한다는 것을 이해하는 것이 중요하다.

조치를 제공하기 위해 우리는 키가 @"shadowPath"이고, 기존 값이 shadowPath이고, 레이어에 이미 bounds.size 인 애니메이션이 있는지 확인합니다. 기존 bounds.size 애니메이션을 찾는 이유는 무엇입니까? 기존 애니메이션에는 지속 시간과 타이밍 기능이 있기 때문에 shadowPath에 애니메이션을 사용해야합니다. 모든 순서에 있다면, 우리는, 애니메이션의 복사본을 만드는 작업에 저장하고 작업 복귀, shadowPath 기존 잡아 :

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event { 
    if (![event isEqualToString:@"shadowPath"]) { return [super actionForLayer:layer forKey:event]; } 

    CGPathRef priorPath = layer.shadowPath; 
    if (priorPath == NULL) { return [super actionForLayer:layer forKey:event]; } 

    CAAnimation *sizeAnimation = [layer animationForKey:@"bounds.size"]; 
    if (![sizeAnimation isKindOfClass:[CABasicAnimation class]]) { return [super actionForLayer:layer forKey:event]; } 

    CABasicAnimation *animation = [sizeAnimation copy]; 
    animation.keyPath = @"shadowPath"; 
    ShadowingViewAction *action = [[ShadowingViewAction alloc] init]; 
    action.priorPath = priorPath; 
    action.pendingAnimation = animation; 
    return action; 
} 

@end 

무엇처럼 액션 모양을합니까? 인터페이스는 다음과 같습니다.

@interface ShadowingViewAction : NSObject <CAAction> 
@property (nonatomic, strong) CABasicAnimation *pendingAnimation; 
@property (nonatomic) CGPathRef priorPath; 
@end 

구현에는 runActionForKey:object:arguments: 메소드가 필요합니다. 이 방법에서는, 우리는 우리가 구원을 멀리 오래된 shadowPath 새로운 shadowPath를 사용하여 actionForLayer:forKey:에서 만든 애니메이션을 업데이트 한 다음 우리는 레이어에 애니메이션을 추가 할 수 있습니다. ARC는 CGPath 개체를 관리하지 않기 때문에

우리는 또한, 저장된 경로의 유지 수를 관리해야합니다.

@implementation ShadowingViewAction 

- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict { 
    if (![anObject isKindOfClass:[CALayer class]] || _pendingAnimation == nil) { return; } 
    CALayer *layer = anObject; 
    _pendingAnimation.fromValue = (__bridge id)_priorPath; 
    _pendingAnimation.toValue = (__bridge id)layer.shadowPath; 
    [layer addAnimation:_pendingAnimation forKey:@"shadowPath"]; 
} 

- (void)setPriorPath:(CGPathRef)priorPath { 
    CGPathRetain(priorPath); 
    CGPathRelease(_priorPath); 
    _priorPath = priorPath; 
} 

- (void)dealloc { 
    CGPathRelease(_priorPath); 
} 

@end 
+1

롭, 이건 대단해! 이것은 정확히 제가 찾던 해결책이었습니다. 나는 이것을 스위프트 (Swift)로 번역했고 여기에 게시 할 것입니다. – horseshoe7

1

이것은 스위프트 (Swift)에 작성된 Rob Mayoff의 답변입니다. 누군가를 구할 수있었습니다.

이 찬성 투표하지 마십시오. Rob Mayoff의 해결책을 Upvote. 그것은 굉장하고 정확합니다.

import UIKit 

class AnimatingShadowView: UIView { 

    struct DropShadowParameters { 
     var shadowOpacity: Float = 0 
     var shadowColor: UIColor? = .black 
     var shadowRadius: CGFloat = 0 
     var shadowOffset: CGSize = .zero 

     static let defaultParameters = DropShadowParameters(shadowOpacity: 0.15, 
                  shadowColor: .black, 
                  shadowRadius: 5, 
                  shadowOffset: CGSize(width: 0, height: 1)) 
    } 

    @IBOutlet weak var contentView: UIView! // no sense in have a shadowView without content! 

    var shadowParameters: DropShadowParameters = DropShadowParameters.defaultParameters 

    private func apply(dropShadow: DropShadowParameters) { 
     let layer = self.layer 
     layer.shadowColor = dropShadow.shadowColor?.cgColor 
     layer.shadowOffset = dropShadow.shadowOffset 
     layer.shadowOpacity = dropShadow.shadowOpacity 
     layer.shadowRadius = dropShadow.shadowRadius 
     layer.masksToBounds = false 
    } 

    override func layoutSubviews() { 
     super.layoutSubviews() 
     let layer = self.layer 
     layer.backgroundColor = nil 

     let contentLayer = self.contentView.layer 
     assert(contentLayer.superlayer == layer, "contentView must be a direct subview of AnimatingShadowView!") 

     self.apply(dropShadow: self.shadowParameters) 

     let radius = contentLayer.cornerRadius 
     layer.shadowPath = UIBezierPath(roundedRect: contentLayer.frame, cornerRadius: radius).cgPath 
    } 

    override func action(for layer: CALayer, forKey event: String) -> CAAction? { 
     guard event == "shadowPath" else { 
      return super.action(for: layer, forKey: event) 
     } 

     guard let priorPath = layer.shadowPath else { 
      return super.action(for: layer, forKey: event) 
     } 

     guard let sizeAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation else { 
      return super.action(for: layer, forKey: event) 
     } 

     let animation = sizeAnimation.copy() as! CABasicAnimation 
     animation.keyPath = "shadowPath" 
     let action = ShadowingViewAction() 
     action.priorPath = priorPath 
     action.pendingAnimation = animation 
     return action 
    } 
} 


private class ShadowingViewAction: NSObject, CAAction { 
    var pendingAnimation: CABasicAnimation? = nil 
    var priorPath: CGPath? = nil 

    // CAAction Protocol 
    func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { 
     guard let layer = anObject as? CALayer, let animation = self.pendingAnimation else { 
      return 
     } 

     animation.fromValue = self.priorPath 
     animation.toValue = layer.shadowPath 
     layer.add(animation, forKey: "shadowPath") 
    } 
}