8

I am first time working with UICollectionView not sure how to do this. I am trying to create an app for tvOS and want to display the menu like airbnb tvos app. I have somehow tried to achieve that particular format didUpdateFocusInContext but the problem is about the first appearance because the first appearance takes place on default points i.e 0,0 of collection view which results in a mess. Heres my code what I have done so far.

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as? ShowCell {
    menuData = dataArray[indexPath.row] as! NSDictionary
    cell.configureCell(menuData.objectForKey("name") as! String)

    return cell
  }
  else {
    return ShowCell()
  }
}

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
  if let previousItem = context.previouslyFocusedView as? ShowCell {
    UIView.animateWithDuration(0.2, animations: { () -> Void in
      previousItem.showImg.frame.size = self.originalCellSize
    })
  }
  if let nextItem = context.nextFocusedView as? ShowCell {
    UIView.animateWithDuration(0.2, animations: { () -> Void in
      nextItem.showImg.frame = CGRectMake(
                                    self.view.frame.width/2 - self.focusCellSize.width/2, 
                                    169.0, 
                                    self.focusCellSize.width, 
                                    self.focusCellSize.height)

    })
  }
}

This is the view which I want to achieve and I have achieved it but for later indexes means index after 1,2enter image description here

This is the starting behaviour when it appears for the first time and when I move the control to it it happens like this enter image description here

enter image description here

This is what i actually want, but i am struggling in getting my focused cell to the middle of the screen and similarly my previous and next on the either sides . I know i am giving frames coordinates explicitly which isn't correct this was just a test case i was running and nothing else but i couldn't find the way to do this

cdpalmer
  • 658
  • 1
  • 7
  • 19
MQ.
  • 365
  • 6
  • 28
  • Wouldn't setting `adjustsImageWhenAncestorFocused` on the image view do the job in your case? – Michał Ciuba Mar 21 '16 at 10:42
  • havent tried that yet, but the thing is i dont have set image only i have to fix all of the cells! – MQ. Mar 21 '16 at 10:45
  • Why aren't you using a UIpageViewController instead of the CollectionView? With what you are trying to achieve, with horizontal scrolling, and animation between items it seems that may be the better choice for you. All you need to do is structure the View (similar to a collection cell) that would be used, i.e. give it the image and the label, then the controller handles the animation and paging. https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPageViewControllerClassReferenceClassRef/ – heading_to_tahiti Mar 28 '16 at 13:27
  • If you want to this feature. Then i am confusing. How will you move the last image of the collection view. – Sankalap Yaduraj Singh Mar 30 '16 at 13:06
  • So the last image will be same as the first image, just try to visualize First image starts from middle and will have one on right similarly when last one will be in middle it will have second last on its right. – MQ. Mar 30 '16 at 22:57

3 Answers3

3

I'd control the focus and the collection content offset (scroll position) separately.

For the content offset you should set the section margins and the inter-item spacing so that you have one cell centred and the adjacent cells visible at the edges. You can get this setup and tested without showing any focus.

Presumably there is difficulty getting the item to move exactly to the centre when you scroll (the focus changes). To resolve this, implement - scrollViewWillEndDragging:withVelocity:targetContentOffset: to find the item at the targetContentOffset and get its centre point (from the layout attributes). With that you can modify the targetContentOffset such that the scroll ends exactly with the item centred.

Now, the focus should be managed by the cell itself, not the collection view. Below is a (slightly large) example of a cell focus change animation. It uses cartography to change image view constraints and applies a transform to a label. You can do something similar depending on how you want the image and label to interact with each other.

Note that the below code also applies a motion effect transform similar to the stock supplied by apple when a UIImageView has focus and has adjustsImageWhenAncestorFocused set. If you don't want that you can simplify and shorten the code quite a bit.

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
    super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)

    if (context.nextFocusedView == self) {
        UIView.animateWithDuration(0.1,
            animations: { () -> Void in
                self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
                    $0.top == $0.superview!.top
                    $0.bottom == $0.superview!.bottom
                    $0.leading == $0.superview!.leading
                    $0.trailing == $0.superview!.trailing
                }
                self.itemLabel.transform = CGAffineTransformMakeTranslation(0, 60)
                self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0).CGColor

                self.layer.shadowOpacity = 1

                self.layoutIfNeeded()
            }, completion: nil)

        let minMaxAngle = 10.0
        let m34 = CGFloat(1.0 / -1250)
        let angle = CGFloat(minMaxAngle * M_PI / 180.0)

        var baseTransform = CATransform3DIdentity
        baseTransform.m34 = m34

        let rotateXmin = CATransform3DRotate(baseTransform, -1 * angle, 1.0, 0.0, 0.0);
        let rotateXmax = CATransform3DRotate(baseTransform, angle, 1.0, 0.0, 0.0);
        let rotateYmin = CATransform3DRotate(baseTransform, angle, 0.0, 1.0, 0.0);
        let rotateYmax = CATransform3DRotate(baseTransform, -1 * angle, 0.0, 1.0, 0.0);

        let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
            type: .TiltAlongVerticalAxis)
        verticalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateXmin)
        verticalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateXmax)

        let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "layer.transform",
            type: .TiltAlongHorizontalAxis)
        horizontalMotionEffect.minimumRelativeValue = NSValue(CATransform3D: rotateYmin)
        horizontalMotionEffect.maximumRelativeValue = NSValue(CATransform3D: rotateYmax)

        let group = UIMotionEffectGroup()
        group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]

        self.addMotionEffect(group)

    }
    else {
        UIView.animateWithDuration(0.3,
            animations: { () -> Void in
                self.imageConstraints = constrain(self.itemImageView, replace: self.imageConstraints!) {
                    $0.top == $0.superview!.top + 20
                    $0.bottom == $0.superview!.bottom - 20
                    $0.leading == $0.superview!.leading + 20
                    $0.trailing == $0.superview!.trailing - 20
                }
                self.itemLabel.transform = CGAffineTransformIdentity
                self.itemLabel.layer.backgroundColor = UIColor.darkGrayColor().colorWithAlphaComponent(0.75).CGColor

                self.layer.shadowOpacity = 0

                self.layoutIfNeeded()
            }, completion: nil)

        for effect in self.motionEffects {
            self.removeMotionEffect(effect)
        }
    }
}
Wain
  • 118,658
  • 15
  • 128
  • 151
  • so i read your answer very carefully. You have categorized things which is indeed very helpful even the first part of the problem is almost solved but still i am unable to center the cell i've tried like several combinations but no luck. So second part(the relation of image and label) goes well :) Thanks very much but still waiting for your response on cell centre thing.. – MQ. Apr 03 '16 at 01:05
  • I've added more detail about the part i guess you have an issue with, the exact end point of the scroll. – Wain Apr 03 '16 at 09:05
0

@Wain has already put a guideline to achieve the mentioned Style here's my accurate answer to this:

override func didUpdateFocusInContext(context: UIFocusUpdateContext,
                                          withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
        if let focusedView = context.nextFocusedView as? ShowCell {
            collectionView.scrollEnabled = false
            let indexPath = collectionView.indexPathForCell(focusedView)!
            focusedView.showImg.frame.size = self.focusImageSize
            focusedView.showLbl.frame.size = self.focusLabelSize
            focusedView.showLbl.font = UIFont.systemFontOfSize(75)


            collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredHorizontally, animated: true)
            self.collectionView.layoutIfNeeded()
        }
        else if let lastFocuedView = context.previouslyFocusedView as? ShowCell{


            collectionView.scrollEnabled = true
           // let indexPath = collectionView.indexPathForCell(lastFocuedView)!

            lastFocuedView.showImg.frame.size = self.originalImageSize
            lastFocuedView.showLbl.frame.size = self.originalLabelSize


            lastFocuedView.showLbl.font = UIFont.systemFontOfSize(70)

            self.collectionView.layoutIfNeeded()


        }

and for the first and last cell you've to give UIEdgeInsets (it is a delegate method of flow layout)

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets{

        return UIEdgeInsets(top: 0.0, left: self.collectionView.frame.width/2 , bottom: 0.0, right: self.collectionView.frame.width/2)



    }

Sincere thanks to Wain for providing an accurate guideline to this question. Cheers!

MQ.
  • 365
  • 6
  • 28
0

The solution is to override the scroll view delegate method scrollViewWillEndDragging here https://stackoverflow.com/a/42117107/1587729 (Swift 3)

Community
  • 1
  • 1
Damien Romito
  • 9,801
  • 13
  • 66
  • 84