Image does not fit properly in collectionCell - swift

In Swift 3. My app has a table view and a detail view with is a view controller. Here I added a collection view. An array of images must be presented. The images adapt themselves beautifully if in portrait I check the content mode in scaler to fill. In ladscape it works in aspectFill. Vice-versa, the layout does not fit. I did the following
in cellForItem At:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = myCollection.dequeueReusableCell(withReuseIdentifier: "cellCollection", for: indexPath) as! CollectionViewCell
cell.cellImage.image = UIImage(named: array[indexPath.item])
if UIDevice.current.orientation.isLandscape{
cell.cellImage.contentMode = .scaleAspectFill
}
else if UIDevice.current.orientation.isPortrait{
cell.cellImage.contentMode = .scaleToFill
}
return cell
}
It does not work. May be I have some inconsistencies from portrait to landscape.
I would appreciate your help. If you want to take a look at the project in git:
[https://github.com/ricardovaldes/tareaFinalHoteles.git1. Thanks in advance.

Eureka. Finally I did solve this problem. With sizeForItem at, the app could function without problems. I was lost or mislead looking for a way to change the image content mode, but this is not the right way. The correct way is resize once you rotate: for this you need the func willTransition toNewCollection: UITraitCollection. Inside you create a variable layout that is of type UICollectionViewFlowLayout. With the help of a conditional, is possible to define how you want the with and heigt after rotation.This post is clear and concise for those curious: UICollectionView Cell Resizing on Device Rotation.
Here the implementation I did in my project: `
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape,
let layout = myCollection.collectionViewLayout as? UICollectionViewFlowLayout{
let with = myCollection.frame.size.width
layout.itemSize = CGSize(width: with, height: 280)
layout.invalidateLayout()
}
else if UIDevice.current.orientation.isLandscape,
let layout = myCollection.collectionViewLayout as? UICollectionViewFlowLayout{
let with = myCollection.frame.size.width
layout.itemSize = CGSize(width: with/2, height: 280)

Related

how to adjust layout of tableview cells having collectionView on screen rotation on all screen sizes?

I have CollectionView embedded in TableView cells to show images posted by a user. Im targeting this app on every device from iPhone 8 to Mac and I do have screen rotation on for iPhones and iPads. This is my first time developing app for all platforms & dealing with screen rotation & after searching online I came up with many solutions for handling layout on screen rotation but none of them worked for TableView cells that contain CollectionView and are repeated.
Here I would like to know the best solutions among these for different situations and how to use them to handle CollectionView inside TableView cells.
The first solution I implemented is to handle everything by overriding viewWillLayoutSubviews() in my main View Controller and wrote something like this:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let index = IndexPath(row: 0, section: 0)
guard let cell = tblHome.cellForRow(at: index) as? PriorityTaskTableViewCell else {
return
}
guard let flowLayout = cell.cvPriorityTask.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
cell.layoutIfNeeded()
cell.heightCvPriorityTask.constant = cell.height + 40
}
I had to add cell.layoutIfNeeded() & cell.heightCvPriorityTask.constant = cell.height + 40 to update cell height after rotation. The above approach worked fine for me on every device but only for one TableView cell as you can see I can access CollectionView present in 1st row of TableView only. This approach did not help me deal with situation where I have multiple rows with CollectionView like in case of a social media feed.
Then I decided to deal with rotation in TableView Cell Subclass and came up with following code inside PriorityTaskTableViewCell:
weak var weakParent: HomeViewController?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
cvPriorityTask.contentInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
cvPriorityTask.delegate = self
cvPriorityTask.dataSource = self
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard
let previousTraitCollection = previousTraitCollection,
self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass ||
self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass
else {
return
}
self.cvPriorityTask?.collectionViewLayout.invalidateLayout()
weakParent?.tblHome.updateConstraints()
DispatchQueue.main.async {
self.cvPriorityTask?.reloadData()
self.weakParent?.tblHome.updateConstraints()
}
}
func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
viewWillTransition(to: size, with: coordinator)
self.cvPriorityTask?.collectionViewLayout.invalidateLayout()
weakParent?.tblHome.updateConstraints()
coordinator.animate(alongsideTransition: { context in
}, completion: { context in
self.cvPriorityTask?.collectionViewLayout.invalidateLayout()
self.weakParent?.tblHome.updateConstraints()
})
}
And this is how I'm setting up CollectionView Layout:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width = 0.0
if traitCollection.horizontalSizeClass == .compact {
width = (weakParent?.view.frame.size.width ?? 200) * 0.88
}
else {
width = (weakParent?.view.frame.size.width ?? 200) * 0.425
}
if traitCollection.userInterfaceIdiom == .mac {
height = width * 0.75
}
else {
height = width * 1.13
}
return CGSize(width: width, height: height)
}
This is again another modified code and it seems to work perfectly for small devices like iPhone 8. But does not have any impact on larger displays on rotation. Maybe it works only for traitCollection.horizontalSizeClass == .compact If I could get this working I would have solved my issue for dealing my repeating CollectionView inside multiple rows but I have no idea why it doesn't work on iPhone 13 Pro Max or iPads.
After trying for hours I removed code and just called cvPriorityTask.collectionViewLayout.invalidateLayout() in layoutSubviews() function and it worked too for all cells so I removed all above code from Cell subclass and left with this only:
override func layoutSubviews() {
super.layoutSubviews()
cvPriorityTask.collectionViewLayout.invalidateLayout()
}
This alone made sure collectionView embedded in TableView cells were changing layout as expected but they started cutting TableView cell as main TableView wasn't being updated so I modified my code to somehow refresh TableView too and came up with something like this:
override func layoutSubviews() {
super.layoutSubviews()
cvPriorityTask.collectionViewLayout.invalidateLayout()
layoutIfNeeded()
heightCvPriorityTask.constant = height + 40
weakParent?.tblHome.rowHeight = UITableView.automaticDimension
weakParent?.tblHome.updateConstraints()
}
It did not work at all. If somehow I could update layout of the main table too from the cell subclass.
So im confused which should be right approach. The first one seemed to be most common one I find on internet but I don't know how to handle multiple cells containing CollectionViews with that approach.
Second one works only for iPhone 8 and I have no idea why. And I don't think reloading collectionView is the right approach but it works for small screens so I wouldn't mind if somehow works for large screens.
The third one seems totally wrong to me but actually works on perfectly for every device. However only cell layout is adjusted and I tried to update layout of whole tableView from Cell Subclass but no luck.
So how to deal with layout issues for CollectionView which is embedded in TableView cells? Am I on right track or there is another better way to deal with this situtaion?

How to add an UICollectionViewLayout programmatically?

I'm trying to setup a UICollectionViewLayout programmatically. I'm using a UICollectionView also without using storyboards, as well as settings its constraints.
I've followed Ray Wenderlich tutorial on the subject with some changes to adapt the code to Swift 3 (as AZCoder2 did https://github.com/AZCoder2/Pinterest).
Since all these examples uses storyboards, I've also introduced some changes to create the UICollectionView and its UICollectionViewLayout:
collectionViewLayout = PinterestLayout()
collectionViewLayout.delegate = self
collectionView = UICollectionView.init(frame: .zero, collectionViewLayout: collectionViewLayout)
The result: I can't see anything. If I change the UICollectionViewLayout with the one that Apple provides (UICollectionViewFlowLayout), at least I can see the cells with their content. If I implement some changes and use the storyboard, everything works great but it's not the way I want to accomplish this. The whole view is made programmatically and the collection view is a part of it.
What am I missing? Is it something to do with the way I instantiate the UICollectionViewLayout? Do I have to register something (for example, as I need to register the reusable cell)?
How about you just create a variable that creates your flow layout for you like this
var flowLayout: UICollectionViewFlowLayout {
let _flowLayout = UICollectionViewFlowLayout()
// edit properties here
_flowLayout.itemSize = CGSize(width: 98, height: 134)
_flowLayout.sectionInset = UIEdgeInsetsMake(0, 5, 0, 5)
_flowLayout.scrollDirection = UICollectionViewScrollDirection.horizontal
_flowLayout.minimumInteritemSpacing = 0.0
// edit properties here
return _flowLayout
}
And then you can set it by calling the variable.
self.collectionView.collectionViewLayout = flowLayout // after initializing it another way
// or
UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
I think this will work.
override init(collectionViewLayout layout: UICollectionViewLayout) {
super.init(collectionViewLayout: layout)
collectionView?.collectionViewLayout = YourCollectionViewLayout()
}
It's possible to do what I was trying to do. Basically, follow the tutorials I suggested in my own question and setup the collection view and its view layout as follow:
collectionViewLayout = PinterestLayout()
collectionViewLayout.delegate = self
collectionView = DynamicCollectionView.init(frame: .zero, collectionViewLayout: collectionViewLayout)
Note that I'm using DynamicCollectionView (instead of UICollectionView). This class is not provided by Apple: I've made my own using the code provided in this post.
Remember that this approach is when you're creating a view programmatically, using constraints. (May be it has another cases of use)
I resolved this issue taking these steps:
1 - Changing UICollectionViewController to a UIViewController with a collectionView inside.
2 - On ViewDidLoad I set the delegates and add the view, as usual. Moreover, I instantiate the ViewLayout and use it to instantiate the CollectionView
var collectionView: UICollectionView?
override func viewDidLoad() {
super.viewDidLoad()
navigationController!.isToolbarHidden = true
let layout = MosaicViewLayout()
layout.delegate = self
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
guard let collectionView = collectionView else {
return
}
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: "CharacterCell")
setupCollectionConstraints()
}
3 - On the ViewLayout the protocol stays like this:
protocol MosaicViewLayoutDelegate:class {
func collectionView(_ collectionView: UICollectionView,
heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat
}
Don't forget to add the instance inside the class
weak var delegate: MosaicViewLayoutDelegate?
4 - Add the delegate extension to you ViewController
extension HomeViewImpl: MosaicViewLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat {
let random = arc4random_uniform(4) + 1
return CGFloat(random * 100)
}
}
enter code here

Tint of the image does not want to change

I am trying to set a new color of the image while the cell of the UICollectionView was selected or deselected. Whenever i do not set a tint color of the image it is working, but i do not wanna have default blue color of it. So What I am doing is :
In func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell function, I am inicialazing my images with code :
let borderWidth = itemBorder.frame.width
let borderHeight = itemBorder.frame.height
myImage = UIImageView(frame: CGRect(x: 0, y: 0, width: borderWidth - 1/4 * borderWidth, height: borderHeight - 1/5 * borderHeight))
myImage.image = UIImage(named: myCollection[indexPath.row])?.withRenderingMode(.alwaysTemplate)
myImage.tintColor = UIColor.groupTableViewBackground
myImage.center = CGPoint(x: tmpCell.bounds.width/2, y: tmpCell.bounds.height/2)
myImage.contentMode = .scaleAspectFit
tmpCell.contentView.addSubview(myImage)
and so on in the didselected and deselected function :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let currentCell: UICollectionViewCell?
switch collectionView {
case myCollectionView:
print("clicked")
currentCell = myCollectionView.cellForItem(at: indexPath)!
currentCell?.tintColor = UIColor.white
case colorsCollectionView:
break
default:
break
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let currentCell: UICollectionViewCell?
switch collectionView {
case vehiclesCollectionView:
print("deselected")
currentCell = myCollectionView.cellForItem(at: indexPath)!
currentCell?.tintColor = UIColor.groupTableViewBackground
case colorsCollectionView:
break
default:
break
}
}
Could someone tell me whats wrong ?
Thanks in advance!
Solution
Solution
Whenever i wanted to update the tint of the color i was pointing on the cell not on the image so basiclly only need to add this into selected or deselected method :
currentCell = myCollectionView.cellForItem(at: indexPath)!
let image = currentCell?.contentView.subviews[1] as! UIImageView
image.tintColor = UIColor.white
I see that you are setting the currentCell?.tintColor but I suppose you will probably have to set the currentCell?.vehicleImageView.image.tintColor
Also I see your code as a bit confusing since you have vehicleImage (which should probably be named vehicleImageView) and myImage which is a UIImage which is being added as a subView to the contentView? I thought it was only possible to add subclasses of UIView as subviews.
I suggest you create an outlet called myImageView in your custom UICollectionViewCell to which you can set the image.tintColor and change cell.myImageView.tintColor in your didSelect and didDeselect
If you do not add them from the storyboard, you can still create a subclass of UICollectionViewCell that has a property called vehicleImageView. You can set the frame and image of this as required in your cellForRow. Now you will have a property which you can refer to in your didSelect and didDeselect as cell.vehicleImageView.image.tintColor.
If you do not want to create a subclass that has a property, you will basically have to loop through all your subviews and find the image view and set the image tintColor there. Setting the tintColor of the UICollectionViewCell WILL NOT solve the problem. You will have to set it to the imageView.image
Hope that helps!

How to set UICollectionViewCell height to 90% of the View (Screen) height?

I'm trying to set the UICollectionViewCell height as 90% of the screen size. So the cell size would look good when using different devices.
The problem is that I'm not sure how to set the cell height programmatically. All cell should look the same size, so there's no need to consider the content, I only want the cell height to be 90% of the screen.
Here is what it looks like in iphone 6 screen:
However, when switching to a smaller device it looks like this:
Thank you in advance for your help!
You can set your UICollectionView's flow layout tile size programmatically. There are a couple of different places you could cause this, I would start with viewWillAppear (in your ViewController class) for starters:
override func viewWillAppear() {
super.viewWillAppear()
if let layout = self.schedulesView.collectionViewLayout as? UICollectionViewFlowLayout {
var cellSize = UIScreen.main.bounds.size // start with the full screen size
cellSize.height *= 0.9 // adjust the height by 90%
layout.itemSize = cellSize // set the layouts item size
}
}
You can set collectionView itemSize by using collectionViewFlowLayout according to device size. Try below method..
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
// All the values are changable according to your needs.
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionInset = UIEdgeInsets(top: 25, left: 3, bottom: 3, right: 3)
layout.minimumInteritemSpacing = 50
layout.minimumLineSpacing = 50
return CGSize(width: self.collectionView.frame.width - 50, height:self.collectionView.frame.height - 100)
}
Note: From the above code you will get box type collectionView itemSize that covers the view accordingly to device size.
Just figured what the problem is with contents inside UICollectionViewCell not updating their size.
Just need to as this to the subclass of UICollectionViewCell
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}

Does anyone Know how to implement this design (in swift)?

The profiles get pulled in from a server but I don't know how to implement this. Had been thinking about a dynamic table view, but I don't know if you can draw cells like that. The pictures have to be clickable.
Add A UICollectionView to your screen. In the collection view cell, place a image view, then use this code to create the blue border and circle image...
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = self.collectionView.dequeueReusableCellWithReuseIdentifier("BasicCell", forIndexPath: indexPath) as UICollectionViewCell
let imageView = cell.viewWithTag(1) as! UIImageView
//border
imageView.layer.borderWidth = 2.0
imageView.layer.borderColor = UIColor.blueColor().CGColor
//make imageView Circle
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
return cell
}
UICollectionView is probably what you're looking for (to display like on your screen capture : sections / items).
UICollectionView Class Reference : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/
It's similar as working with UITableView. Still, you might want to read some tutorials about it.
What you want is UICollectionView. Think of it as the generic version of UITableView.