Reputation: 3064
I have a situation where I want a single subview to not be clipped to the superview's bounds, while the rest of the superview's subviews are clipped to its bounds. Is there a way to exclude a certain UIView
from being clipped to its superview's bounds. Similarly, can a UIView
specify which subviews it does/doesn't want to clip to its own bounds?
As far as code goes, I am using Koloda to create UIView
s that are similar to Tinder's swiping cards. I have a collection view whose cells are essentially Koloda views. Whenever a card starts panning, I set its cell's collection view's clipsToBounds
property to false
. This allows the cell to be panned above the navigation bar without being clipped. When the pan ends, the view's cell's collection view's clipsToBounds
property is set back to true
.
import UIKit
import Koloda
class LikedUserCollectionViewCell: UICollectionViewCell {
@IBOutlet var swipeView: KolodaView!
var parentVC: MyViewController!
}
extension LikedUserCollectionViewCell: KolodaViewDelegate, KolodaViewDataSource {
func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView {
//return a KolodaView, this code isn't important to the issue
return KolodaView()
}
func kolodaNumberOfCards(_ koloda: KolodaView) -> Int {
return 1
}
func kolodaPanBegan(_ koloda: KolodaView, card: DraggableCardView) {
self.parentVC.collectionView.bringSubviewToFront(self)
//Here, instead of unclipping all of the collection view's subviews, I would like to only unclip the one containing the subview that is being panned
self.parentVC.collectionView.clipsToBounds = false
}
func kolodaPanFinished(_ koloda: KolodaView, card: DraggableCardView) {
//Once the pan is finished, all of the collection view's subviews can be clipped again
self.parentVC.collectionView.clipsToBounds = true
}
func koloda(_ koloda: KolodaView, didSwipeCardAt index: Int, in direction: SwipeResultDirection) {
//Not relevant to the question
}
}
The problem is, when there are cells that are partially scrolled off the collection view, but not yet removed from the collection view as they have not completely scrolled off the collection view, they now become visible as the collection view's clipsToBounds
is now set to true
. This is why I want to be able to dictate which of the collection view's subviews are clipped to its bounds. This behavior can be seen in the below gif:
So, in summary, is there a way to, instead of unclipping all subviews from their superview's bounds via superview.clipsToBounds = false
, I could only unclip a single subview from its superview's bounds?
Upvotes: 1
Views: 1018
Reputation: 16327
The simple solution here is that since you want to make an exception for a single view, don't move the collectionView cell. take a snapshotView and move the snapshot instead; it can be anywhere else in the hierarchy and does not need to be a child of the collection view. You can use UIView.convert(from:) to convert the cells coordinates into the appropriate parent view's coordinate space to properly place your snapshot.
Edit from OP:
I was able to solve my issue by taking Josh's advice and updating my code to the following:
import UIKit
import Koloda
class LikedUserCollectionViewCell: UICollectionViewCell {
@IBOutlet var swipeView: KolodaView!
var parentVC: MyViewController!
//DraggableCardView is a custom Koloda class
var draggableCardView: DraggableCardView!
}
extension LikedUserCollectionViewCell: KolodaViewDelegate, KolodaViewDataSource {
func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView {
//return a KolodaView, this code isn't important to the issue
return KolodaView()
}
func kolodaNumberOfCards(_ koloda: KolodaView) -> Int {
return 1
}
func kolodaPanBegan(_ koloda: KolodaView, card: DraggableCardView) {
let snapshotView = self.swipeView.snapshotView(afterScreenUpdates: false)!
snapshotView.accessibilityIdentifier = "snapshotView"
self.parentVC.view.addSubview(snapshotView)
snapshotView.frame.origin = self.parentVC.view.convert(swipeView.viewForCard(at: 0)!.frame.origin, from: swipeView)
draggableCardView = card
swipeView.viewForCard(at: 0)?.isHidden = true
}
func koloda(_ koloda: KolodaView, draggedCardWithPercentage finishPercentage: CGFloat, in direction: SwipeResultDirection) {
let snapshotView = self.parentVC.view.subviews.first(where: { $0.accessibilityIdentifier == "snapshotView" })!
snapshotView.frame.origin = self.parentVC.view.convert(draggableCardView.frame.origin, from: swipeView)
}
func kolodaPanFinished(_ koloda: KolodaView, card: DraggableCardView) {
let snapshotView = self.parentVC.view.subviews.first(where: { $0.accessibilityIdentifier == "snapshotView" })!
draggableCardView = nil
snapshotView.removeFromSuperview()
swipeView.viewForCard(at: 0)?.isHidden = false
}
func koloda(_ koloda: KolodaView, didSwipeCardAt index: Int, in direction: SwipeResultDirection) {
//Not relevant to the question
}
}
Upvotes: 1
Reputation: 1
Looking at your interface, why is the collectionView above the (navigation bar)?
You shouldn't need any clipping in this situation, as long as the collectionView is behind the navigation bar.
Either call sendSubview(toBack: collectionView) or set the collectionView.layer.zPosition to a larger value.
Upvotes: 0