Adding images to table view cell problem using stack views - swift

in my prototype cell I have a horizontal stack view and I connected that to my UITableViewCell and I defined an update function In my UITableViewCell that adds multiple images to the stack view and I called the function In TableViewController cellForRowAt but nothing nothing happens.
//inside UITableViewCell
#IBOutlet weak var lineStack: UIStackView!
func update(_ imageName: String){
let numberOfCopies = Int(deviceWidth/50)
let startX = (Int(deviceWidth) - (numberOfCopies*50))/2
xValue = xValue + startX
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
xValue = xValue + heightValue
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: xValue , y: 0, width: widthValue, height: heightValue)
lineStack.addSubview(imageView)
}
}
//inside TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Line", for: indexPath) as! LineTableViewCell
let imageName = imagesName[indexPath.row]
cell.update(imageName)
return cell
}

Your post indicates you want your images to be 50 x 50 points... use auto-layout constraints with .addArrangedSubview():
class TestCell: UITableViewCell {
#IBOutlet var lineStack: UIStackView!
func update(_ imageName: String) {
let numberOfCopies = Int(deviceWidth/50)
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
lineStack.addArrangedSubview(imageView)
}
}
}
EDIT Sample result, with stack view centered horizontally:
Stack view is set to:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 0
and this is the layout with constraints:
Note that the stack view has a Width constraint of 10, but its Priority is 250 ... that allows the stack view to stretch horizontally, while keeping IB satisfied with the constraints.

For the line:
lineStack.addSubview(imageView)
Instead of addSubview() you need to do it with addArrangedSubview(). The former is the regular way of adding a subview to a view, whereas the latter is specifically for UIStackView and tells the view to insert it properly.

Related

UICollectionView: Resizing cells with animations and custom layouts

I have a collection view with a custom layout and a diffable datasource. I would like to resize the cells with an animation.
With a custom layout: it looks like the content of the cells scaled to the new size during the animation. Only when the cells reaches its final size, it is finally redrawn (see animation below)
With a stock flow layout the subviews are laid out correctly during the animation: the label remains at the top, no resizing and same for the switch.
So I would assume that the issue is in the layout implementation, what could be added to the layout allow a correct layout during the animation?
Description
The custom layout used in this example is FSQCollectionViewAlignedLayout, I also got it with the custom I used in my code. I noticed that for some cases, it is important to always return the same layout attributes objects, which FSQCollectionViewAlignedLayout doesn't do. I modified it to not return copies, and the result is the same.
For the cells (defined in the xib), the contentMode of the cell and its contentView are set to .top. The cell's contentView contains 2 subviews: a label (laid out in layoutSubviews) and a switch (laid out with constraints, but its presence has no effect on the animation).
The code of the controller is:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
enum Section {
case main
}
#IBOutlet weak var collectionView : UICollectionView!
var layout = FSQCollectionViewAlignedLayout()
var dataSource : UICollectionViewDiffableDataSource<Section, String>!
var cellHeight : CGFloat = 100
override func loadView() {
super.loadView()
layout.defaultCellSize = CGSize(width: 150, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
cell.label.text = item
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(["1", "2"], toSection: .main)
dataSource.apply(snapshot)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: cellHeight)
}
#IBAction func resizeAction(_ sender: Any) {
if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
UIView.animate(withDuration: 0.25) {
layout.defaultCellSize.height += 50
let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
UIView.animate(withDuration: 0.25) {
self.cellHeight += 50
let invalidation = UICollectionViewFlowLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
}
}
class Cell : UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
}
}
I spent a developer support ticket on this question. The answer was that custom animations when reloading cells are not supported.
So as a workaround, I ended up creating new cells instances that I add to the view hierarchy, then add autolayout constraints so that they overlap exactly the cells being resized. The animation is then run on those newly added cells. At the end of the animation, these cells are removed.

Access CollectionView Cell Content (change width of inner view)

I want to have a uiview as cell background with changing width. This should be used as a progress-/ statusbar.
The problem:
I can’t change the width of the statusbar from my collectionViewController (only from the CollectionViewCellController)....
I manually set the width to 5 in layoutSubviews(). Then I changed it to 80 in setStatusBar(...) which I called in the other class. The width stays at 5 :(
Here is my code
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var statusbar: UIView!
override func layoutSubviews() {
// cell rounded section
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = true
super.layoutSubviews()
statusbar.frame = CGRect(x: 0, y: 0, width: 5, height: self.frame.height)
}
}
extension MyCollectionViewCell{
func setStatusbar(completedTasks: Int, totalTasks: Int){
print(self.frame.width)
let newWidth = 80 //(self.frame.width * (CGFloat(completedTasks / totalTasks)))
statusbar.backgroundColor = .orange
statusbar.frame = CGRect(x: 0, y: 0, width: newWidth, height: Int(self.frame.height))
reloadInputViews()
}
}
And this is where I call the function setStatusbar(...) - where I want to change the width, but DOESNT work:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.myLabel.text = self.items[indexPath.row] // The row value is the same as the index of the desired text within the array.
cell.backgroundColor = UIColor(rgb: 0xF444444)
cell.setStatusbar(completedTasks: 5, totalTasks: 25) //!!! HERE !!!
return cell
}
Can someone help me find the problem why the statusbar uiview doesn’t change its with from 5 to 80?
Thank you!

Cached shadow path for UITableView

I want to cache a shadow path to make the performance of UITableView better.
I've read https://yalantis.com/blog/mastering-uikit-performance/ that
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
would stop offscreen shadows, however using the following function
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Cell data: \(data[indexPath.row])"
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
cell.imageView?.layer.shadowRadius = 8
cell.imageView?.layer.shadowOffset = CGSize(width: 3, height: 3)
cell.imageView?.layer.shadowOpacity = 0.5
cell.imageView?.layer.cornerRadius = 20
cell.imageView?.image = UIImage(named: "PlaceholderImage")
return cell
}
produces a result where some cells have the shadow and some don't.
How can I implement caching of shadow paths to all cells in a simple UITableView
The problem is these lines:
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
If the cell's imageView has never been assigned an image, its frame is .zero and so its bounds is .zero. So the shadowPath ends up with a cgPath that is just a point, and no shadow appears.
The way I would solve this is that I would not use the built-in imageView property at all. I'd use a custom cell class with my own image view whose frame I can control, instead of the built-in imageView which plays these sorts of tricks.
if let shadowPath = self.shadowPathCache {
cell.imageView?.layer.shadowPath = shadowPath
} else if let rect = cell.imageView?.bounds {
self.shadowPathCache = UIBezierPath(rect: rect).cgPath
cell.imageView?.layer.shadowPath = self.shadowPathCache
}

TableView in iCarousel finding nil while unwrapping optional value

So, I have a carousel of "BillSplitters" and on each carousel item it should display the uniques items a BillSplitter is having. So I'm getting fatal error: unexpectedly found nil while unwrapping an Optional value Normally i can slowly hone in on an error like this i find the issue but when following on from a breakpoint line by line it enters into the iCarousel code which i cant follow. Im also sure theres nothing going wrong in i carousel as if i dont addSubview(tableView) then it runs fine. It also seems to create the first couple of tableviews and add them fine and then gets the error. Here is the code im using:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let splitter = allBillSplitters[carouselIndex]
if (splitter.items?.count)! > 0 {
return (splitter.items?.count)!
} else {
TableViewHelper.EmptyMessage("\(splitter.name!) has no items to pay for.\nGo back to assign some items to their name.", tableView: tableView)
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ItemCell = tableView.dequeueReusableCell(withIdentifier: "SplitterItemCell") as! ItemCell
let itemsSet = allBillSplitters[carouselIndex].items
let items = itemsSet?.allObjects as! [Item]
let item = items[indexPath.row]
let count = item.billSplitters?.count
if count! > 1 {
cell.name!.text = "\(item.name!) split \(count!) ways"
cell.price!.text = "£\(Double(item.price)/Double(count!))"
} else {
cell.name!.text = item.name!
cell.price!.text = "£\(item.price)"
}
return cell
}
func numberOfItems(in carousel: iCarousel) -> Int {
return allBillSplitters.count
}
I've read in a few places that I should remove the if let view = view statement in the following function as it's not re-using the items and always creating new ones. If I leave it in I get the same error immediately when creating the first carousel item and when I remove it, it happens on the creating the third i carousel item.
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
carouselIndex = index
var splitterView: UIImageView
var nameLabel: UILabel
var emailLabel: UILabel
var totalLabel: UILabel
var tableView: UITableView
let splitter = allBillSplitters[index]
//reuse view if available, otherwise create a new view
if let view = view as? UIImageView {
splitterView = view
//get a reference to the label in the recycled view
nameLabel = splitterView.viewWithTag(1) as! UILabel
emailLabel = splitterView.viewWithTag(2) as! UILabel
totalLabel = splitterView.viewWithTag(3) as! UILabel
tableView = splitterView.viewWithTag(4) as! UITableView
} else {
let height = carousel.contentView.frame.height - 85
let width = carousel.contentView.frame.width - 80
//don't do anything specific to the index within
//this `if ... else` statement because the view will be
//recycled and used with other index values later
splitterView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
splitterView.layer.cornerRadius = 10
splitterView.clipsToBounds = true
splitterView.image = UIImage(data: splitter.image as! Data, scale:1.0)
splitterView.contentMode = .scaleAspectFit
splitterView.backgroundColor = UIColor(netHex: 0xCA9875)
let viewWidth = Int(splitterView.frame.width)
nameLabel = UILabel(frame: CGRect(x: 5, y: 0, width: viewWidth, height: 30))
nameLabel.backgroundColor = .clear
nameLabel.backgroundColor?.withAlphaComponent(0.1)
nameLabel.textAlignment = .left
nameLabel.font = nameLabel.font.withSize(20)
nameLabel.tag = 1
emailLabel = UILabel(frame: CGRect(x: 5, y: 30, width: viewWidth, height: 15))
emailLabel.backgroundColor = .clear
emailLabel.textAlignment = .left
emailLabel.font = emailLabel.font.withSize(15)
emailLabel.tag = 2
totalLabel = UILabel(frame: CGRect(x: 5, y: 45, width: viewWidth, height: 15))
totalLabel.backgroundColor = .clear
totalLabel.textAlignment = .left
totalLabel.font = totalLabel.font.withSize(15)
totalLabel.tag = 3
let tableViewHeight = height - 65
let frame = CGRect(x: 0, y: 65, width: width, height: tableViewHeight)
tableView = UITableView(frame: frame)
tableView.delegate = self
tableView.dataSource = self
tableView.tag = 4
totalLabel.backgroundColor = .clear
splitterView.addSubview(nameLabel)
splitterView.addSubview(emailLabel)
splitterView.addSubview(totalLabel)
splitterView.addSubview(tableView)
}
//set item label
//remember to always set any properties of your carousel item
//views outside of the `if (view == nil) {...}` check otherwise
//you'll get weird issues with carousel item content appearing
//in the wrong place in the carousel
nameLabel.text = "\(allBillSplitters[index].name!)"
emailLabel.text = "\(allBillSplitters[index].email!)"
totalLabel.text = "£\(allBillSplitters[index].total)"
return splitterView
}
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
switch option {
case .spacing:
return value * 1.2
case .fadeMin:
return 0.0
case .fadeMinAlpha:
return 0.3
case .fadeMax:
return 0.0
default:
return value
}
}
I've looked all over and can't find a solution so any help would be great. Thanks
I'm an idiot. Forgot the following:
tableView.register(CarouselTableViewCell.classForCoder(), forCellReuseIdentifier: "carouselTableViewCell")
in tableviews cellForRowAt function

UIScrollView in UICollectionViewCell with Swift

I'm new in IOS. I try to create view images app that show fullscreen size images can zoom and when user zoom out images smaller than fullscreen size, image return fullscreen size and my idea is use UICollectionView set paging Enable , scrollDirection Horizontal. So my FlowLayout will be like this
var screenSize: CGRect!
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.mainScreen().bounds
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenSize.width, height: screenSize.height)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
self.collectionView!.collectionViewLayout = layout
self.view.addSubview(collectionView!)
my CollectionViewCell identifier name "Cell" I drag ScrollView in it and drag image in that ScrollView
I outlet that two item in my CollectionViewCell Class like this
import UIKit
class MyCollectionCell: UICollectionViewCell, UIScrollViewDelegate{
#IBOutlet weak var scrollCell: UIScrollView!
#IBOutlet weak var imgTest: UIImageView!
}
next step in CollectionViewController set my dummy image something like this
var imageName:[String] = ["audi.png", "benz.png", "flok.png", "audi.png", "benz.png", "flok.png", "audi.png", "benz.png", "flok.png"]
and go to cellForItemAtIndexPath
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! MyCollectionCell
cell.layer.borderWidth = 0
cell.frame.size.width = screenSize.width
cell.frame.size.height = screenSize.height
// set cell size become fullscreen
cell.imgTest.image = UIImage(named: imageName[indexPath.row])
cell.imgTest.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:cell.imgTest.image!.size)
//call my dummy image to cell
cell.scrollCell.addSubview(cell.imgTest)
cell.scrollCell.contentSize = cell.imgTest.image!.size
let scaleHeight = cell.scrollCell.frame.size.height / cell.scrollCell.contentSize.height
let scaleWidth = cell.scrollCell.frame.size.width / cell.scrollCell.contentSize.width
let minScale = min(scaleWidth, scaleHeight)
cell.scrollCell.minimumZoomScale = minScale
cell.scrollCell.maximumZoomScale = 1.0
cell.scrollCell.zoomScale = minScale
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollviewDoubleTapped")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTapsRequired = 1
cell.scrollCell.addGestureRecognizer(doubleTapRecognizer)
I run this project and the problem is
scroll size don't equal my image size but over
my image can't zoom (I already check "user interaction enabled")
I try using pinch gesture to solve it but I don't know how to set minimumzoomscale to it and the images can't scroll when zoom in
By this code I try using image from json with SDWebImage I change code in cellForItemAtIndexPath from call dummy image to this
cell.imgTest.sd_setImageWithURL(NSURL(string: dataArray[indexPath.row]), placeholderImage: UIImage(named: "loading.png"))
but this way Thread 1 : EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP,subcode=0x0)
Where I go from here can anyone guide me in swift please
instead of a collection view use a scrollview, much easier, and more flexible. Check out this link very good tutorial.. You can merge the scrolled page and scroll zoom. UIScrollView Tutorial: Getting Started