可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How can I add a header view / top view (not section header) at the top of a UICollectionView
?
It should act excactly as UITableView
's tableHeaderView
property.
So it needs to sit on top of the first section header view (before the section at index 0), scroll along with the rest of the content, and have user interaction.
The best I've come up with so far is to make a special XIB (with MyCollectionReusableView
subclass of UICollectionReusableView
as the File's owner) for the first section header view that is big enough to also contain my subviews in header, it's kind of a hack, I think, and I haven't managed to detect touches.
Not sure if I can make my MyCollectionReusableView
subclass to allow touches or there's a better way.
回答1:
I've put a view at the top of a collection view by just adding a UIView as a subview of the collection view. It does scroll up with the collection view and it has normal UIView user interaction. This works ok if you have no section header, but doesn't work if you do. In that situation, I don't see anything wrong with the way you're doing it. I'm not sure why you're not detecting touches, they work fine for me.
回答2:
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.frame.size.height) collectionViewLayout:flowlayout];
self.collectionView.contentInset = UIEdgeInsetsMake(50, 0, 0, 0);
UIImageView *imagev = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"015.png"]];
imagev.frame = CGRectMake(0, -50, 320, 50);
[self.collectionView addSubview: imagev];
[self.view addSubview: _collectionView];
I use the attribute contentInset
to insert a frame to the top of the UICollectionView
, then I add the image view to it, and it succeeds. I think it can act excactly as UITableView
's tableHeaderView
property. What do you think?
回答3:
The best way to achieve this is to make a section header for the first section. Then set the UICollectionViewFlowLayout's property:
@property(nonatomic) BOOL sectionHeadersPinToVisibleBounds
or swift
var sectionHeadersPinToVisibleBounds: Bool
to NO (false for swift). This will ensure that the section header scrolls continuously with the cells and does not get pinned to the top like a section header normally would.
回答4:
I ended up using a UICollectionView extension to add my custom header. Using a specific tag, I'm able to fake the stored property to identify my custom header (all transparent from outside).
You might have to scroll to a specific offset if the layout of the UICollectionView is not completed when you add your custom header.
extension UICollectionView {
var currentCustomHeaderView: UIView? {
return self.viewWithTag(CustomCollectionViewHeaderTag)
}
func asssignCustomHeaderView(headerView: UIView, sideMarginInsets: CGFloat = 0) {
guard self.viewWithTag(CustomCollectionViewHeaderTag) == nil else {
return
}
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
headerView.frame = CGRect(x: sideMarginInsets, y: -height + self.contentInset.top, width: self.frame.width - (2 * sideMarginInsets), height: height)
headerView.tag = CustomCollectionViewHeaderTag
self.addSubview(headerView)
self.contentInset = UIEdgeInsets(top: height, left: self.contentInset.left, bottom: self.contentInset.bottom, right: self.contentInset.right)
}
func removeCustomHeaderView() {
if let customHeaderView = self.viewWithTag(CustomCollectionViewHeaderTag) {
let headerHeight = customHeaderView.frame.height
customHeaderView.removeFromSuperview()
self.contentInset = UIEdgeInsets(top: self.contentInset.top - headerHeight, left: self.contentInset.left, bottom: self.contentInset.bottom, right: self.contentInset.right)
}
}
}
CustomCollectionViewHeaderTag refers to the tag number you give to your header. Make sure it is not the tag of anotherView embedded in your UICollectionView.
回答5:
I think the best way to accomplish this it to subclass UICollectionViewLayout(UICollectionViewFlowLayout) and add the needed header or footer attributes.
Since all the items including the supplementary view in UICollectionView layout according to its collectionViewLayout
property, it is the collectionviewlayout
that decides whether there is a header(footer) or not.
Use contentInset
is easier, but there may be problem when you want to hide or show the header dynamically.
I have written a protocol to accomplish this, which could be adopted by any subclass of UICollectionViewLayout to make it have a header or footer. You can find it here.
The main idea is to create a UICollectionViewLayoutAttributes
for the header and return it in layoutAttributesForElements(in rect: CGRect)
if needed, you will have to modify all the other attributes, all of their locations should be moved down by header height because the header is above them all.
回答6:
private var PreviousInsetKey: Void?
extension UIView {
var previousInset:CGFloat {
get {
return objc_getAssociatedObject(self, &PreviousInsetKey) as? CGFloat ?? 0.0
}
set {
if newValue == -1{
objc_setAssociatedObject(self, &PreviousInsetKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
else{
objc_setAssociatedObject(self, &PreviousInsetKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
func addOnCollectionView(cv: UICollectionView){
if self.superview == nil{
var frame = self.frame
frame.size.width = SCREEN_WIDTH
cv.frame = frame
self.addOnView(view: cv)
let flow = cv.collectionViewLayout as! UICollectionViewFlowLayout
var inset = flow.sectionInset
previousInset = inset.top
inset.top = frame.size.height
flow.sectionInset = inset
}
}
func removeFromCollectionView(cv: UICollectionView){
if self.superview == nil{
return
}
self.removeFromSuperview()
let flow = cv.collectionViewLayout as! UICollectionViewFlowLayout
var inset = flow.sectionInset
inset.top = previousInset
flow.sectionInset = inset
previousInset = -1
}
func addOnView(view: UIView){
view.addSubview(self)
self.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0)
let widthConstraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: view.frame.size.width)
let heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: view.frame.size.height)
let topConstraint = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0)
view.addConstraints([leftConstraint, widthConstraint, heightConstraint, topConstraint])
self.layoutIfNeeded()
view.layoutIfNeeded()
}
}