I am having difficulty getting UICollectionView Content Insets Adjustment Behavior setting to work. It appears to be modifying the location of 'ghost' ReusableView instead of my header view. In this screenshot the ghost view is selected, the header view I'm trying to control is green (its bg color).
I have tried using both the story board and programatic options, both are moving the position of the ghost view instead of my visible view. I cannot find where these ghost views are being generated nor can I see them in storyboard.
screenshot of ghost views
Here is my reusable view function:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if(kind == "UICollectionElementKindSectionHeader"){
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "questionHeader", for: indexPath) as! QuestionheaderCollectionReusableView
if(self.mode == .Test){
view.scoreRing.alpha = 0.5
view.scoreRing.style = .ontop
}else{
view.updateScore(session: QKSession.default)
}
if((activeQuestion) != nil){
view.questionLabel.text = activeQuestion?.question
view.updateProgress(session: QKSession.default, question: activeQuestion!)
}else{
view.questionLabel.text = "Please select a MQF"
}
return view
}else{
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "questionFooter", for: indexPath) as! QuestionFooterCollectionReusableView
return view
}
}
Has anyone else seen anything like this?
Related
I'm trying to create hints for new users in iOS application, like to open profile click here, etc. I have a collection view and inside it I have elements that I fetch from server.
I need to show hint near the first element. My code:
var tipView = EasyTipView(
text: "Выбирай предмет и начинай обучение",
preferences: EasyTipView.globalPreferences
)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let section = sections[indexPath.section]
switch section {
case .fav_subjects:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FavSubjectsCollectionViewCell", for: indexPath) as! FavSubjectsCollectionViewCell
let favSubjects = model[.fav_subjects] as! [Subjects.FavSubject]
let cellModel = favSubjects[indexPath.item]
cell.configure(with: cellModel)
//here I'm trying to render hint near first element of collectionView
if indexPath.section == 0 && indexPath.item == 0 {
let view = cell.contentView
tipView.show(forView: view)
}
return cell
case .all_subjects:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AllSubjectsCollectionViewCell", for: indexPath) as! AllSubjectsCollectionViewCell
let allSubjects = model[.all_subjects] as! [Subjects.Subject]
let cellModel = allSubjects[indexPath.item]
cell.configure(with: cellModel)
return cell
}
}
}
My problem is when I open screen for the first time my hint displays on the top of the screen and not near the first element, but when I switch to another screen and then go back, hint displays exactly where expected.
I suggest that my problem is screen rendered for the first time before all items fetched from server so tried to use layoutIfNeeded() to rerender when the size is changed but it doesn't help:
tipView.layoutIfNeeded()
if indexPath.section == 0 && indexPath.item == 0 {
let view = cell.contentView
self.tipView.layoutIfNeeded()
self.tipView.show(forView: view)
}
Also I have tried to use viewDidAppear, because I thought that if screen is already rendered than I will have element rendered and I will show hint in the right place for the first render but it turns out that in viewDidAppear cell is nil:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let cell = collectionView.cellForItem(at: IndexPath(item: 0, section: 0))
}
Incorrect(when screen opened first time):
Correct:
I'm new to iOS so I think I missed something important, please help.
The method cellForItem(at:) gets called before the cells gets displayed on screen. In fact, at this point the final frame of the cell is not yet determined, it will happen later when the cell gets added to the view hierarchy and the layout pass happens.
In some cases even if cellForItem(at:) gets called, the cell may never appear on screen.
If you always display the hint near the first element, perhaps it will be easier to use the collection view's frame as reference instead of the first cell unless you need the hint to scroll along with the cell.
Alternatively, add the hint as a subview to the cell's contentView and use Auto Layout to define its position inside the cell. Don't forget to remove the hint from the view hierarchy when appropriate because the same cell may end up being dequeued for another index path during scroll.
I have a collection view with 3 sections, the first two sections work fine with UICollectionViewFlowLayout but the 3rd one needs a custom implementation.
I understand that a collection view can only have one layout, so I've been trying to only perform work in the overriden prepare function if I'm in the right section. This is where it tends to fall apart, I can't work out a straightforward way to figure out which section the cell is in which I'm calculating layout attributes for.
Not sure if there's a better approach than conditionally performing calculations in the prepare function either.
A point in the right direction would be great!
I think this is a good approach:
1- Create your custom section with its own .xib file
class CustomSection: UICollectionReusableView {
override func awakeFromNib() {
super.awakeFromNib()
}
}
2- Implement the UICollectionViewDelegateFlowLayout delegate method
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// In case its a section header (UICollectionElementKindSectionFooter for footer)
if kind == UICollectionElementKindSectionHeader {
// This will be the section number
let section = indexPath.section
if section == 3 {
let customSection = collectionView.dequeueReusableSupplementaryView(ofKind: kind,withReuseIdentifier: "CustomSection", for: indexPath) as! CustomSection
return customSection
}
else {
//TODO: Return your default section
}
}
}
3- Don't forget to register the section with your UICollectionView
let customSectionNib = UINib.init(nibName: "CustomSection", bundle: nil)
collectionView.register(customSectionNib, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "CustomSection")
update
now images displays as they are loaded, but there still a problem I can't solve.
The images overlaps the content of the cell even the three views are in a vertical stack (first is the image, the next two are label views).
I wonder how can a view overlap other views in a stackview
now my talbe view delegate method looks like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell!
if let contents = contents {
let content = contents[indexPath.row]
if let imageUrl = content.imageUrl, let url = URL(string: imageUrl) {
cell = tableView.dequeueReusableCell(withIdentifier: "ImageContentRow", for: indexPath) as! ContentWithImageTableViewCell
Alamofire.request(url).responseImage(completionHandler: { (response) in
if let image = response.result.value {
cell.imageView?.image = image
cell.setNeedsLayout()
}
})
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "ContentRow", for: indexPath) as! ContentTableViewCell
}
cell.contentTitleVeiw.text = content.title
cell.contentLeadView.text = content.lead
}
return cell!
}
I'm very new to iOS. I've already tutorials and watched video courses and now I would like to implement my very first app.
I try to read a feed and display the content's title, lead and image (if it has any).
I defined a cell, with an UIMageVeiw, and two UILables, at last I embedded them into a vertical StackView (I set distribution to Fill).
Everything works well, but images don't display automatically, just if I click on the cell, or sroll the TableVeiw.
And even if I set the image view to Aspect Fit (and embedded it into the top tof the vertical stack view), when the image displays it keeps it's original size and overlaps the cell content.
I've trying to find out what I do wrong for two days, but I can't solve it.
I try display data like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell!
if let contents = contents {
let content = contents[indexPath.row]
if let imageUrl = content.imageUrl, let url = URL(string: imageUrl) {
cell = tableView.dequeueReusableCell(withIdentifier: "ImageContentRow", for: indexPath) as! ContentWithImageTableViewCell
cell.imageView?.af_setImage(withURL: url)
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "ContentRow", for: indexPath) as! ContentTableViewCell
}
cell.contentTitleVeiw.text = content.title
cell.contentLeadView.text = content.lead
}
return cell!
}
The image view cell's view hierarchy looks like this:
My list looks like this after I start my app and data displayed:
After I click on a cell that should display an image or scroll it out and back the result is this:
At least this is my list view controller with the two prototype cells
(one for contents with image, one for without image)
You need to notify the tableView that the image was loaded and that it has to redraw the cell.
Try changing
cell.imageView?.af_setImage(withURL: url)
to
cell.imageView?.af_setImage(withURL: url, completion: { (_) in
self.tableView.beginUpdates()
self.tableView.setNeedsDisplay()
self.tableView.endUpdates()
})
Take a look at my answer here - in the end you might need to explicitly set height on the imageView.
I am Trying to add a Tap Gesture Recognizer to the header of my UICollection view, but no matter what, I can't get the numberOfPostsViewTapped() function to fire off. I've been trying for hours, and have tried using other UI elements such as other views or labels in the header view, but nothing is helping. Some guidance would be much appreciated.
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader: // only checking header - no footer on this view
// use an external class for the header UICollectionViewCell in order to set outlets on a non-reusable cell
// if you try to set outlets on a reusable cell, such as a header, it will fail
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Header", forIndexPath: indexPath) as! ProfileCollectionViewHeader
// dynamically set user profile information
headerView.usernameTextLabel.text = user?.name
headerView.numberOfPostsTextLabel.text = user?.numberOfPosts != nil ? "\(user!.numberOfPosts!)" : "0"
let numberOfPostsViewSelector : Selector = #selector(self.numberOfPostsViewTapped)
let viewPostsViewGesture = UITapGestureRecognizer(target: self, action: numberOfPostsViewSelector)
viewPostsViewGesture.numberOfTapsRequired = 1
viewPostsViewGesture.delaysTouchesBegan = true
headerView.numberOfPostsTextLabel.userInteractionEnabled = true;
headerView.numberOfPostsTextLabel.addGestureRecognizer(viewPostsViewGesture)
return headerView
default:
assert(false, "Unexpected element kind")
}
}
func numberOfPostsViewTapped(sender: UITapGestureRecognizer){
print("HErE")
}
Check your frames. Is your UICollectionView within its frame?
If a view is out of its frame, and doesn't clip to its bounds, it will show the view's contents, though user interaction will not work with it.
I think you need to set userInteractionEnabled to true to your headerView so that the tapping would reach your label which is a child of your headerView.
headerView.userInteractionEnabled = true
Made few changes to your code and this worked for me
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
guard
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AddLinksReusableView", for: indexPath) as? AddLinksReusableView else {
fatalError("Invalid view type")
}
let numberOfPostsViewSelector : Selector = #selector(self.imgVuTapped)
let viewPostsViewGesture = UITapGestureRecognizer(target: self, action: numberOfPostsViewSelector)
headerView.profileImg.isUserInteractionEnabled = true
viewPostsViewGesture.numberOfTapsRequired = 1
viewPostsViewGesture.delaysTouchesBegan = true
headerView.profileImg.addGestureRecognizer(viewPostsViewGesture)
return headerView
default:
assert(false, "Invalid element type")
}
}
#objc func imgVuTapped (sender: UITapGestureRecognizer){
print("HErE, Its working")
}
Happy Codding
I have a CollectionView issue, I have a video showing the problem detailed below. When I click one cell it moves in a weird manner.
Here is my code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedFilter = indexPath.row
if filters[indexPath.row] != "Todo" {
filteredNews = news.filter { $0.category == filters[indexPath.row] }
} else {
filteredNews = news
}
tableView.reloadData()
collectionView.reloadData()
}
My Cell is moving, (Just the last cell, don't know why).
I think it might be related to collectionView.reloadData() But I need to do that for updating the green bar you can see on this Video when I select a Cell.
How can I make it not move? Someone had had a similar problem?
I noticed you reloaded a tableView during collectionView didSelectItemAt. If that tableView is a superView of your collectionView that will be the exact reason why you are having this abnormal behaviour.
If it were not, I can offer 3 solutions:
This library have a view controller subclass that can create the effect you want to show.
Manually create a UIView/UIImageView that is not inside the collectionView but update it's position during the collectionView's didSelectItemAt delegate method to but visually over the cell instead - this would require some calculation, but your collectionView will not need to reload.
You can attempt to only reload the two affected cells using the collectionView's reloadItem(at: IndexPath) method.
Know that when you reload a table/collection view, it will not change the current visible cell. However any content in each cell will be affected.
Finally I Solve it! I removed collectionView.reloadData() and added my code to change colors inside didSelectItemAt changing current selected item and old selected item (I created a Variable to see which one was the old selected item).
If someone interested, here is my code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let oldSelectedFilter = selectedFilter
if selectedFilter != indexPath.row {
let oldIndexPath = IndexPath(item: oldSelectedFilter, section: 0)
selectedFilter = indexPath.row
if filters[indexPath.row] != "Todo" {
filteredNews = news.filter { $0.category == filters[indexPath.row] }
} else {
filteredNews = news
}
if let cell = collectionView.cellForItem(at: indexPath) as? FiltersCollectionViewCell {
cell.selectedView.backgroundColor = MainColor
}
if let cell = collectionView.cellForItem(at: oldIndexPath) as? FiltersCollectionViewCell {
cell.selectedView.backgroundColor = UIColor(red:0.31, green:0.33, blue:0.35, alpha:1.0)
}
tableView.reloadData()
}
}