web-dev-qa-db-fra.com

Comment créer un effet de parallaxe dans UITableView avec UIImageView dans leurs cellules prototypes

Je crée une application dans iOS 8.4 avec Swift.

J'ai un UITableView avec un UITableViewCell personnalisé qui comprend un UILabel et UIImageView. Tout cela est assez simple et tout se passe bien.

J'essaie de créer un effet de parallaxe similaire à celui démontré dans cette démo .

J'ai actuellement ce code dans mon tableView.cellForRowAtIndexPath

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = self.tableView.dequeueReusableCellWithIdentifier("myitem", forIndexPath: indexPath) as! MixTableViewCell
    cell.img.backgroundColor = UIColor.blackColor()
    cell.title.text = self.items[indexPath.row]["title"]

    cell.img.image = UIImage(named: "Example.png")

    // ideally it would be cool to have an extension allowing the following
    // cell.img.addParallax(50) // or some other configurable offset

    return cell
}

Ce bloc existe à l'intérieur d'une classe qui ressemble à class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... }

Je sais également que je peux écouter les événements de défilement dans ma classe via func scrollViewDidScroll.

A part ça, l'aide est appréciée!

19
ded

Je l'ai compris! L'idée était de le faire sans implémenter de bibliothèques supplémentaires, surtout compte tenu de la simplicité de l'implémentation.

Tout d'abord ... dans la classe de vue de table personnalisée Cell, vous devez créer une vue wrapper. Vous pouvez sélectionner votre UIImageView dans la cellule Prototype, puis choisir Editor > Embed in > View. Faites glisser les deux dans votre cellule en tant que prises, puis définissez clipToBounds = true pour la vue conteneur. (n'oubliez pas de définir les mêmes contraintes que votre image.

class MyCustomCell: UITableViewCell {
    @IBOutlet weak var img: UIImageView!
    @IBOutlet weak var imgWrapper: UIView!
    override func awakeFromNib() {
        self.imgWrapper.clipsToBounds = true
    }
}

Ensuite, dans votre sous-classe UITableViewController (ou délégué), implémentez le scrollViewDidScroll - à partir de là, vous mettrez à jour en permanence le UIImageView's .frame propriété. Voir ci-dessous:

override func scrollViewDidScroll(scrollView: UIScrollView) {
    let offsetY = self.tableView.contentOffset.y
    for cell in self.tableView.visibleCells as! [MyCustomCell] {
        let x = cell.img.frame.Origin.x
        let w = cell.img.bounds.width
        let h = cell.img.bounds.height
        let y = ((offsetY - cell.frame.Origin.y) / h) * 25
        cell.img.frame = CGRectMake(x, y, w, h)
    }
}

Voir ceci en action .

34
ded

Je n'étais pas trop satisfait de la solution de @ ded nécessitant une vue wrapper, j'ai donc trouvé une autre qui utilise la mise en page automatique et est assez simple.

Dans le storyboard, il vous suffit d'ajouter votre imageView et de définir 4 contraintes sur ImageView:

  • Menant à ContentView (ie Superview) = 0
  • Trailing to ContentView (ie Superview) = 0
  • Espace supérieur vers ContentView (c.-à-d. Superview) = 0
  • Hauteur de ImageView (définie à 200 ici, mais elle est quand même recalculée en fonction de la hauteur des cellules)

enter image description here

Les deux dernières contraintes (en haut et en hauteur) doivent référencer les prises à votre UITableViewCell personnalisé (dans l'image ci-dessus, double-cliquez sur la contrainte dans la colonne la plus à droite, puis affichez l'inspecteur de connexion - l'icône est une flèche dans un cercle)

Votre UITableViewCell devrait ressembler à ceci:

class ParallaxTableViewCell: UITableViewCell {

    @IBOutlet weak var parallaxImageView: UIImageView!

    // MARK: ParallaxCell

    @IBOutlet weak var parallaxHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var parallaxTopConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()

        clipsToBounds = true
        parallaxImageView.contentMode = .ScaleAspectFill
        parallaxImageView.clipsToBounds = false
    }
  }

Donc, fondamentalement, nous disons à l'image de prendre autant d'espace que possible, mais nous la clipsons sur le cadre de la cellule.

Votre TableViewController devrait maintenant ressembler à ceci:

class ParallaxTableViewController: UITableViewController {
    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return cellHeight
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! ParallaxTableViewCell
        cell.parallaxImageView.image = … // Set your image
        cell.parallaxHeightConstraint.constant = parallaxImageHeight
        cell.parallaxTopConstraint.constant = parallaxOffsetFor(tableView.contentOffset.y, cell: cell)
        return cell
    }

    // Change the ratio or enter a fixed value, whatever you need
    var cellHeight: CGFloat {
        return tableView.frame.width * 9 / 16
    }

    // Just an alias to make the code easier to read
    var imageVisibleHeight: CGFloat {
        return cellHeight
    }

    // Change this value to whatever you like (it sets how "fast" the image moves when you scroll)
    let parallaxOffsetSpeed: CGFloat = 25

    // This just makes sure that whatever the design is, there's enough image to be displayed, I let it up to you to figure out the details, but it's not a magic formula don't worry :)
    var parallaxImageHeight: CGFloat {
        let maxOffset = (sqrt(pow(cellHeight, 2) + 4 * parallaxOffsetSpeed * tableView.frame.height) - cellHeight) / 2
        return imageVisibleHeight + maxOffset
    }

    // Used when the table dequeues a cell, or when it scrolls
    func parallaxOffsetFor(newOffsetY: CGFloat, cell: UITableViewCell) -> CGFloat {
        return ((newOffsetY - cell.frame.Origin.y) / parallaxImageHeight) * parallaxOffsetSpeed
    }

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let offsetY = tableView.contentOffset.y
        for cell in tableView.visibleCells as! [MyCustomTableViewCell] {
            cell.parallaxTopConstraint.constant = parallaxOffsetFor(offsetY, cell: cell)
        }
    }
}

Remarques:

  • il est important d'utiliser tableView.dequeueReusableCellWithIdentifier ("CellIdentifier", forIndexPath: indexPath) et non tableView.dequeueReusableCellWithIdentifier ("CellIdentifier"), sinon l'image ne sera pas compensée tant que vous n'aurez pas commencé à faire défiler

Donc, vous l'avez, parallaxe UITableViewCells qui devrait fonctionner avec n'importe quelle mise en page, et peut également être adapté à CollectionViews.

27
Nycen

Cette méthode fonctionne avec la vue de table et la vue de collection.

  • tout d'abord, créez la cellule pour la vue de table et placez-y la vue d'image.

  • définissez la hauteur de l'image légèrement plus que la hauteur de la cellule. si la hauteur de cellule = 160, laissez la hauteur de l'image à 200 (pour créer l'effet de parallaxe et vous pouvez le modifier en conséquence)

  • mettez ces deux variables dans votre viewController ou n'importe quelle classe où votre délégué tableView est étendu

let imageHeight:CGFloat = 150.0
let OffsetSpeed: CGFloat = 25.0
  • ajoutez le code suivant dans la même classe
 func scrollViewDidScroll(scrollView: UIScrollView) {
    //  print("inside scroll")

    if let visibleCells = seriesTabelView.visibleCells as? [SeriesTableViewCell] {
        for parallaxCell in visibleCells {
            var yOffset = ((seriesTabelView.contentOffset.y - parallaxCell.frame.Origin.y) / imageHeight) * OffsetSpeedTwo
            parallaxCell.offset(CGPointMake(0.0, yOffset))
        }
    }
}
  • où seriesTabelView est mon UItableview

  • et laisse maintenant aller à la cellule de ce tableauVoir et ajouter le code suivant

func offset(offset: CGPoint) {
        posterImage.frame = CGRectOffset(self.posterImage.bounds, offset.x, offset.y)
    }
  • étaient posterImage est mon UIImageView

Si vous voulez implémenter ceci dans collectionView, changez simplement le tableauView vairable en votre variable collectionView

et c'est tout. je ne sais pas si c'est la meilleure façon. Mais cela fonctionne pour moi. J'espère que ça marchera pour toi aussi. et faites-moi savoir s'il y a un problème

2
Muneef M

Après avoir combiné les réponses de @ded et @Nycen, je suis arrivé à cette solution, qui utilise la vue intégrée, mais modifie la contrainte de disposition (une seule d'entre elles):

  1. Dans Interface Builder, incorporez la vue d'image dans une UIView. Pour cette vue, faites [√] Clips to bounds archivé Affichage> Dessin

  2. Ajoutez les contraintes suivantes de l'image à afficher: gauche et droite, centre verticalement, hauteur

  3. Ajustez la contrainte de hauteur pour que l'image soit légèrement plus haute que la vue

enter image description here

  1. Pour la contrainte Aligner le centre Y, créez une sortie dans votre UITableViewCell

  2. Ajoutez cette fonction dans votre contrôleur de vue (qui est soit UITableViewController ou UITableViewControllerDelegate)

    private static let screenMid = UIScreen.main.bounds.height / 2
    private func adjustParallax(for cell: MyTableCell) {
        cell.imageCenterYConstraint.constant = -(cell.frame.Origin.y - MyViewController.screenMid - self.tableView.contentOffset.y) / 10
    }
    

Remarque: en modifiant le nombre magique 10 vous pouvez modifier la force d'application de l'effet et en supprimant le - symbole de l'équation, vous pouvez changer la direction de l'effet

  1. Appelez la fonction à partir du moment où la cellule est réutilisée:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCellId", for: indexPath) as! MyTableCell
        adjustParallax(for: cell)
        return cell
    }
    
  2. Et aussi lorsque le défilement se produit:

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        (self.tableView.visibleCells as! [MyTableCell]).forEach { cell in
            adjustParallax(for: cell)
        }
    }
    
1
Vitalii