Setting imageView.image causes unbounded memory growth - swift

Steps
Create UIImages in array
Load UIImage into reusable collection view cell
Scroll
View memory XCode
Code that repros the issue
import UIKit
class Cell: UICollectionViewCell {
static let ReuseIdentifier = "reuseID"
var imageView: UIImageView
override init(frame: CGRect) {
imageView = UIImageView()
super.init(frame: frame)
addSubview(imageView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
imageView.frame = bounds
super.layoutSubviews()
}
func configureWith(image: UIImage) {
imageView.image = image
layoutSubviews()
}
}
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var images:[UIImage] = []
var collectionView: UICollectionView
required init?(coder: NSCoder) {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.ReuseIdentifier)
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(collectionView)
collectionView.frame = view.frame
collectionView.backgroundColor = .green
collectionView.dataSource = self
collectionView.delegate = self
loadMoreImages()
collectionView.reloadData()
}
func loadMoreImages() {
for _ in 0...50 {
images.append(cloneImage(image: UIImage(named: "bigImage")!))
}
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(images.count)
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.ReuseIdentifier, for: indexPath) as! Cell
cell.configureWith(image: images[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return view.frame.size
}
}
func cloneImage(image:UIImage) -> UIImage {
let newCgIm = image.cgImage!.copy()!
let newImage = UIImage(cgImage: newCgIm, scale: image.scale, orientation: image.imageOrientation)
return newImage
}
Video demo: https://i.stack.imgur.com/QRDWl.gif
Things that resolve the issue
Make UIImages optional, and set UIImages to nil after they've been displayed
Comment out imageView.image = image
Not using cloneImage, and instead images.append(UIImage(named: "bigImage")!)
Any idea what UIImageView is doing under the hood that causes unbounded memory growth? Seems like it's allocating a bunch of memory in the UIImage object that never gets released.

Related

Collectionview in NSObject Class doesn't work?

I wrote a class contain a collectionview to show in main viewcontroller,
but it was "never" shows the cell data. And the cell background color wasn't change to black..(change color is only for test, not my purpose)..
(The collectionView can be showed correctly)
Where I should to correct it?
class CellClass: NSObject,
UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDelegateFlowLayout
{
let cellid = "cellid"
let cv : UICollectionView = {
let fl = UICollectionViewFlowLayout()
let v = UICollectionView(frame: .zero,
collectionViewLayout: fl)
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.white
return v
}()
override init() {
super.init()
print("init")
cv.delegate = self
cv.dataSource = self
cv.register(Cell.self,
forCellWithReuseIdentifier: cellid)
if let window = UIApplication.shared.keyWindow {
window.addSubview(cv)
cv.frame = CGRect(x: 0,
y: window.frame.height - 300,
width: window.frame.width,
height: 300)
}
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
print("numberOfItemsInSection")
return 3
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("cell")
let vc = collectionView.dequeueReusableCell(withReuseIdentifier: cellid,
for: indexPath) as! Cell
vc.lbl.text = "test"
vc.backgroundColor = UIColor.black
return vc
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 300, height: 100)
}
}
class Cell: UICollectionViewCell {
let lbl : UILabel = {
let t = UILabel()
t.translatesAutoresizingMaskIntoConstraints = false
return t
} ()
override init(frame: CGRect) {
super.init(frame: frame)
lbl.frame = CGRect(x: 0,
y: 0,
width: 200,
height: 30)
addSubview(lbl)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I could be wrong but I have a feeling you do something like this in your viewDidLoad
let cellClass = CellClass()
As soon as the didLoad function completes its execution, cellClass no longer persists in order to be the datasource and delegate.
You can try this by adding a deinit to your CellClass
deinit {
print("Cell Class Deinit")
}
And I believe it will be called
What I suggest instead is to persist the object beyond viewDidLoad
class AVPlayerScroll: UIViewController, UIGestureRecognizerDelegate
{
var cellClass: CellClass?
override func viewDidLoad()
{
super.viewDidLoad()
cellClass = CellClass()
}
I believe this should give you the results you are looking for
you can try couple of things here.
You can either try to set the constraints programmatically. Something like this.
NSLayoutConstraint.activate([
lbl.leadingAnchor.constraint(equalTo: self.leadingAnchor),
lbl.topAnchor.constraint(equalTo: self.topAnchor),
lbl.widthAnchor.constraint(equalToConstant: 200),
actionSheetView.heightAnchor.constraint(equalToConstant: 30)])
You can try to assign the frame to the UILabel (lbl) subview in layoutSubviews() method.

How to update a UICollectionViewCell Label?

I am trying to change a text label in a UICollectionViewCell, but for some reason it just will not update. Any help would be aprreciated. Thanks!
You can use didSet to make your code a bit reactive.
class CollectionViewCell : UICollectionViewCell{
var text = "" {
didSet{
self.label.text = text
}
}
private var label : UILabel = {
let label = UILabel()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CollectionViewClass : UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
var collectionView = UICollectionView()
let id = "cell ID"
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: self.view.frame)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: id)
collectionView.dataSource = self
collectionView.delegate = self
self.view.addSubview(collectionView)
collectionView.backgroundColor = .white
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as! CollectionViewCell
cell.text = "some text"
cell.backgroundColor = .red
return cell
}
}
In the above code, cell's text value is given everytime it's loaded. Whenever text is set, didset is called which updates the label value.

Make UICollectionViewCells with UIImageView height based on image

I want to make a UICollectionView with the cells the width of the screen, but the height is based on the aspect ratio of the UIImageView inside.
In my current implementation all of the UICollectionViewCell instances are squares and this isn't what I want.
I want to use auto layout somehow to make the size of the image the width of the screen, but the relevant height depends on the aspect ratio of the image used.
Everything is done programatically in code.
My code:
class ViewController: UIViewController {
var data = ["1","2","3","4","5","6","7","8","1","2","3","4","5","6","7","8"]
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(SubclassedCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
self.collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1)
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
}
with the cell
class SubclassedCollectionViewCell: UICollectionViewCell {
var hotelImageView: UIImageView = {
var hotelView = UIImageView()
hotelView.contentMode = .scaleAspectFill
hotelView.clipsToBounds = true
return hotelView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(hotelImageView)
hotelImageView.translatesAutoresizingMaskIntoConstraints = false
hotelImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
hotelImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
hotelImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
hotelImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCell(image: String) {
if let image : UIImage = UIImage(named: image) {
hotelImageView.image = image
}
}
}
First, calculate the ratio based on the width and multiply by collection view width you can get a new height based on width.
Final Code: Remove itemSize from the loadview function. And calculate height and return item size in sizeForItemAt method.
loadView
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1) <-- Remove this
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let iamgeData = UIImage(named: self.data[indexPath.item]) else {
return .zero
}
let width = collectionView.bounds.width
let heightOnWidthRatio = iamgeData.size.height / iamgeData.size.width
let height = width * heightOnWidthRatio
return CGSize(width: width, height: height)
}
}

Label, font programmatically

I need help in this view.
I have to solve these things:
- instead of the 2 "duck" labels, I need to have different label.text with different fonts. I used this method here, in which I assign all the fonts to my datasource and assign the label to it by changing the font. the final result must be, for example, "duck" "ciccio" and "another"..
At the moment the view in which to insert the text is not connected to the colors or the fonts, I have to be able to insert the text and then if I click on a color it automatically changes the color to the written text and so the same thing for the fonts ..
What do I have to do?
I am attaching part of the code, only the one in which I put the "duck" label and the controller of the whole view.
that I have now
class CollectionFont: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
weak var delegate: FontDelegate?
var collectionView: UICollectionView?
let cellSpacing:CGFloat = 1
let indexPath: IndexPath
var datasource: [UIFont] = []
var imageArray: Array<UIImageView> = []
var onceOnly = false
init(datasource: [UIFont], index: IndexPath = [0,0]) {
self.indexPath = index
self.datasource = Theme.CustomFontsFamily.customFonts
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.delegate = self
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewLayout())
collectionView!.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView!)
collectionView!.activate(constraint(edgesTo: self.view))
collectionView!.backgroundColor = Theme.Colors.sky
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionView!.setCollectionViewLayout(collectionViewFlowLayout, animated: true)
collectionViewFlowLayout.scrollDirection = .horizontal
collectionViewFlowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
collectionViewFlowLayout.minimumLineSpacing = 0
collectionViewFlowLayout.minimumInteritemSpacing = 0
collectionView!.register(FontCell.self, forCellWithReuseIdentifier: FontCell.reuseIdentifier)
collectionView!.delegate = self
collectionView!.dataSource = self
collectionView!.isPagingEnabled = true
collectionView!.bounces = false
self.collectionView?.reloadData()
}
class FontRound {
let font: UILabel
init(font: UILabel) {
self.font = font
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.datasource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FontCell.reuseIdentifier, for: indexPath) as! FontCell
cell.array.font = self.datasource[indexPath.row]
return cell
}
//UICollectionViewDelegateFlowLayout - constraint della collecion view da innestare
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.size.width/4
let height = UIScreen.main.bounds.size.width/4
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
debugPrint("sucaaaa")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView!.contentOffset
visibleRect.size = collectionView!.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
guard let indexPath = collectionView!.indexPathForItem(at: visiblePoint) else { return }
print(indexPath[1])
self.delegate?.sincronizeScroll(indexPath: indexPath)
}
internal func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if !onceOnly {
//set the row and section you need.
collectionView.scrollToItem(at: self.indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
onceOnly = true
}
}
}
final class CollectionFontView: UICollectionView {
init(_ collectionViewLayout: UICollectionViewLayout) {
super.init(frame: .zero, collectionViewLayout: collectionViewLayout)
self.backgroundColor = UIColor.red
self.showsHorizontalScrollIndicator = false
register(FontCell.self, forCellWithReuseIdentifier: FontCell.reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class FontCell: UICollectionViewCell {
static let reuseIdentifier = "FontCell_RID"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
var cellID: String?
var array = UILabel().then{
$0.text = "duck"
$0.font = UIFont(name:"Jellee-Roman",size:15)
$0.contentMode = UIView.ContentMode.scaleAspectFit
$0.layer.cornerRadius = (((UIScreen.main.bounds.size.width/8)*0.8)/2)
}
func setupViews(){
self.addSubview(self.array)
self.array.activate([
array.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.8),
array.heightAnchor.constraint(equalTo: self.array.widthAnchor),
array.centerXAnchor.constraint(equalTo: self.centerXAnchor),
array.centerYAnchor.constraint(equalTo: self.centerYAnchor),
])
}
func setContents(container: UILabel) {
for sv in self.contentView.subviews {
sv.removeFromSuperview()
}
self.contentView.addSubview(container)
container.activate(constraint(edgesTo: self.contentView))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Cells not appearing in UICollectionView

I don't understand why my cells aren't showing:
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "pouet"
lazy var customCollectionView : UICollectionView = {
let layout = UICollectionViewLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.gray
cv.dataSource = self
cv.delegate = self
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
customCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
addSubview(customCollectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: customCollectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: customCollectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = customCollectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
print("test")
return cell
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now my print("test") is not showing either, meaning that the method is actually not called, right? So what is wrong ?
Change this line of code:
let layout = UICollectionViewLayout()
To:
let layout = UICollectionViewFlowLayout()
UICollectionViewLayout is abstract class you can't use it directly.