HeroTransitions / Hero

Elegant transition library for iOS & tvOS
https://HeroTransitions.github.io/Hero/
MIT License
22.04k stars 1.72k forks source link

Apple TV #61

Closed gpolyansky closed 6 years ago

gpolyansky commented 7 years ago

Please, could you add apple tv support

lkzhao commented 7 years ago

I am not familiar with tvOS. Not sure how UIKit works on Apple TV. Should be easy to port if it has core animation and the 'snapshotAfterScreenUpdate' API. Maybe you can put up a PR to contribute to the project.😀

fruitcoder commented 7 years ago

Hey @lkzhao ! I have made a minimal working tvOS example app with Hero from a fork. It's not good enough for me to open a pull request, so you can take a look first here I had to put some conditional compiling in the DebugView because sliders and pinchgestures weren't available in tvOS.

The transition did have a subtle stutter at the beginning, maybe because of the visual effect view, but it could be fixable by tweaking some Hero parameters.

herotvos

gpolyansky commented 7 years ago

@fruitcoder I have an Apple TV application in production now and in few weeks I will redesign it and make tvOS support for Hero and send PR to @lkzhao if you don't do it faster :)

lkzhao commented 7 years ago

@fruitcoder I checked out your branch, Looks awesome. It is quite useable already. @f0r3s1 I believe we can get this merged soon, so don't put too much work into this.

@fruitcoder I have just pushed a commit into the master branch that fix the flash that you are seeing. please rebase and let me know if that fixes things.

One thing I have noticed is that the Debug Plugin really doesn't do much in tvOS without the slider. Can we just make the Debug Plugin unavailable in tvOS instead? Later, we can build some gesture to mimic the slider function. But that shouldn't be blocking.

fruitcoder commented 7 years ago

I'll do it right now. Now HeroDebugPlugin and HeroDebugView aren't compiled for tvOS and are, therefore, unavailable.

fruitcoder commented 7 years ago

Your fix works perfectly, the flicker is gone! PR at #71

lkzhao commented 7 years ago

I updated the master with a some ported examples. Just got myself familiar with tvOS's focus model. One thing I have noticed with ImageView with adjustImageWhenFocused enabled, is that I have no way of snapshotting the focused imageView. Any of you guys know how this can be done?

fruitcoder commented 7 years ago

Do you have a branch that illustrates the issue? What image do you get when snapshotting a focused image view?

lkzhao commented 7 years ago

@fruitcoder Yes, please checkout the tvOS examples under the imageGallery tab:

tvos 2

The result I got from snapshotting a focused view does not contain the focused effect.

fruitcoder commented 7 years ago

Ah I see! I'll take a look tomorrow

fruitcoder commented 7 years ago

Oh damn... I've had my issues with this super private API of the image view focus behaviour and here we go again. The problem is that the focused image view cannot be snapshotted, because there are CATransformLayers involved. See at: https://developer.apple.com/reference/quartzcore/calayer/1410909-render which is used under the hood of snapshotView:afterScreenUpdates. Comparing layer tree of the cells once with "Adjusts image when focused" and once without shows us:

with focus:

<HeroTvOSExamples.ImageCell: 0x7f8004e18020; baseClass = UICollectionViewCell; frame = (435 64; 307.2 307.2); opaque = NO; layer = <CALayer: 0x6100002245e0>>
   | <UIView: 0x7f8004e18440; frame = (0 0; 307.2 307.2); gestureRecognizers = <NSArray: 0x61000005dfa0>; layer = <CALayer: 0x610000224600>>
   |    | <UIImageView: 0x7f8004e18600; frame = (0 0; 307 307); autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x610000224620>>
   |    |    | <_UIStackedImageContainerView: 0x7f8004e18ce0; frame = (0 0; 307 307); layer = <_UIStackedImageContainerLayer: 0x7f8004e18ea0>>
   |    |    |    | <CALayer: 0x610000225080> (layer)
   |    |    |    | <CALayer: 0x610000225060> (layer)
   |    |    |    |    | <CATransformLayer: 0x610000225280> (layer)
   |    |    |    |    |    | <CALayer: 0x610000225400> (layer)
   |    |    |    | <CALayer: 0x610000225240> (layer)
   |    |    |    | <CALayer: 0x610000225320> (layer)
   |    |    |    |    | <CATransformLayer: 0x6100002252e0> (layer)
   |    |    |    |    |    | <CATransformLayer: 0x610000225420> (layer)
   |    |    |    |    |    |    | <CALayer: 0x610000225300> (layer)
   |    |    |    |    |    |    | <CALayer: 0x610000226780> (layer)
   |    |    |    | <UIView: 0x7f8004e19340; frame = (0 0; 1 1); layer = <CALayer: 0x610000224f40>>

without focus:

<HeroTvOSExamples.ImageCell: 0x7febc940a020; baseClass = UICollectionViewCell; frame = (64 64; 307.2 307.2); opaque = NO; layer = <CALayer: 0x618000032e80>>
   | <UIView: 0x7febc9409d30; frame = (0 0; 307.2 307.2); gestureRecognizers = <NSArray: 0x6180000555d0>; layer = <CALayer: 0x618000032fc0>>
   |    | <UIImageView: 0x7febc9410910; frame = (0 0; 307 307); autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x618000033320>>

Going deeper on the image view's layer's sublayers:

po (collectionView.cellForItem(at: indexPath) as! ImageCell).imageView.layer.sublayers.first!.sublayers!
â–¿ Optional<Array<CALayer>>
  â–¿ some : 5 elements
    - 0 : <CALayer:0x608000039f40; position = CGPoint (153.22 153.5); bounds = CGRect (0 0; 505 511); delegate = <_UIStackedImageContainerLayer: 0x7f9dacb114c0>; contents = <CGImage 0x6080001d4a00>
    <<CGColorSpace 0x600000028380> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)>
        width = 280, height = 280, bpc = 8, bpp = 32, row bytes = 1120 
        kCGImageAlphaPremultipliedFirst | kCGImageByteOrder32Little 
        is mask? No, has mask? No, has matte? No, should interpolate? Yes; opacity = 0.5; contentsScale = 1; transform = CATransform3D (1 0 0 0; 0 1 0 0; 0 0 1 0; 0 50 0 1); contentsCenter = CGRect (0.5 0.5; 0 0); shadowColor = <CGColor 0x608000093a60> [<CGColorSpace 0x618000027a40> (kCGColorSpaceICCBased; kCGColorSpaceModelMonochrome; Generic Gray Gamma 2.2 Profile; extended range)] ( 0 1 ); zPosition = -51; animations = [position=<CABasicAnimation: 0x61800002f440>]>
    - 1 : <CALayer:0x608000039e20; position = CGPoint (153.5 153.5); bounds = CGRect (0 0; 307 307); delegate = <_UIStackedImageContainerLayer: 0x7f9dacb114c0>; sublayers = (<CATransformLayer: 0x60800003a2e0>); allowsGroupOpacity = YES; >
    - 2 : <CALayer:0x60800003a6c0; position = CGPoint (153.314 153.5); bounds = CGRect (0 0; 307 307); contents = <CGImage 0x6080001d4550>
    <<CGColorSpace 0x600000028380> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)>
        width = 512, height = 512, bpc = 8, bpp = 32, row bytes = 2048 
        kCGImageAlphaPremultipliedLast | 0 (default byte order) 
        is mask? No, has mask? No, has matte? No, should interpolate? Yes; hidden = YES; masksToBounds = YES; backgroundColor = (null); animations = [position=<CABasicAnimation: 0x61800003c0e0>]>
    - 3 : <CALayer:0x60800003a720; position = CGPoint (153.314 153.5); bounds = CGRect (0 0; 307 307); sublayers = (<CATransformLayer: 0x60800003ab20>); mask = <CATransformLayer: 0x60800003aee0>; filters = (
    "<CAFilter: 0x61800005a0d0>"
); animations = [position=<CABasicAnimation: 0x61800002dac0> filters.brightness.inputAmount=<CABasicAnimation: 0x61800002d120>]>
    - 4 : <CALayer:0x60800003b420; position = CGPoint (0.5 0.5); bounds = CGRect (0 0; 1 1); delegate = <UIView: 0x7f9dacb133b0; frame = (0 0; 1 1); alpha = 0; layer = <CALayer: 0x60800003b420>>; opaque = YES; allowsGroupOpacity = YES; opacity = 0>

This is the first hint of the visible bounds of the scaled image (CGRect (0 0; 505 511)), so a super hacky way would be to take that bounds, calculate a transform that would scale the original image to that bounds, and take this as a snapshot for the fromView. But since internals can change, this would have to be tested for every tvOS release 😔

fruitcoder commented 7 years ago

Oh! I just saw you already got the correct frame via imageView.focusedFrameGuide.layoutFrame 😄

fruitcoder commented 7 years ago

I managed to get the frame correct when coming from a focused view, but I could not yet make it revert to the focused frame because I rely on the cell still being focused: https://github.com/fruitcoder/Hero/tree/tv-os-focus-fix

To make the reverse working I thought about taking the focus from the current view away and forcing the focus back to the previously focused item, but I don't know if that works.

lkzhao commented 7 years ago

Nice! I feel like this is pretty good already. UIKit refocus the imageView after the dismiss transition with a scale animation. Seems fluid. One thing I have noticed it that white glare suddenly disappears. Is that possible we can focus the imageView we created during the transition to get that glare effect? Or just snapshotting the focused view using resizableSnapshotViewFromRect

lkzhao commented 7 years ago

I also saw @felix-dumit used a snapshot method with custom bounds when capturing the background for UIVisualEffectView: https://github.com/lkzhao/Hero/issues/26 https://github.com/lkzhao/Hero/commit/7d9f0f11a4c7e94d26fb48f4b0ad25dcd51e10ea

fruitcoder commented 7 years ago

Hm, would be interesting to make that work. The glare seems to be one of the custom layers that don't aren't part of the composition being screenshotted. But maybe if the transition view returns the image view snapshot as its preferredFocusEnvironments or preferredFocusedView and calling setNeedsFocusUpdate() and updateFocusIfNeeded() on the transition view might produce the glare.