Instruments show “_NSContiguousstring” memory leak when scrolling UITableView - ios

I have a UItableView which consist of 10 element. I opened Instruments to catch the memory leaks and when I scroll tableView, It started to give memory leaks. In Instruments I tried to find which causes the leaks but can't figure out, It says "_NScontiguousstring" for whole leaks.
I found some solutions for Objective-C which they check If cell is nil in "CellForRowAt" function. I don't think it is useful for Swift but I tried and as expected It doesn't work.
Memory Leak UITableView
My question is what can cause this kind of memory leak?
Controller Class;
class TableViewController: UITableViewController {
let teamModel = TeamModel(uid: "adsada", name: "First Team ", idea: "idea 1", slogan: "Slogan 1", university: "dasda", image: "info", isActive: true)
let teamModel2 = TeamModel(uid: "adsada", name: "Team 2", idea: "idea 2", slogan: "adasd", university: "dasda", image: "info", isActive: true)
let teamModel3 = TeamModel(uid: "adsada", name: "Team 3", idea: "idea 3", slogan: "adasd", university: "dasda", image: "info", isActive: true)
let teamModel4 = TeamModel(uid: "adsada", name: "Team 4", idea: "idea 4", slogan: "adasd", university: "dasda", image: "info", isActive: true)
let teamModel5 = TeamModel(uid: "adsada", name: "Team 5", idea: "idea 5", slogan: "adasd", university: "dasda", image: "info", isActive: true)
let teamModel6 = TeamModel(uid: "adsada", name: "Team 6", idea: "idea 6", slogan: "adasd", university: "dasda", image: "info", isActive: true)
let teamModel7 = TeamModel(uid: "adsada", name: "Team 7", idea: "idea 7", slogan: "adasd", university: "dasda", image: "info", isActive: true)
var data: [TeamModel] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(mainTableCell.self, forCellReuseIdentifier: "mainTableCell")
data = [teamModel,teamModel2,teamModel3,teamModel4,teamModel5,teamModel6,teamModel7]
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mainTableCell", for: indexPath) as! mainTableCell
let cell_data = data[indexPath.row]
cell.cell_data = cell_data
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
}
Cell Class;
class mainTableCell: UITableViewCell{
var cell_data: TeamModel?{
didSet{
guard let unwrappedCell = cell_data else { return }
if let url = unwrappedCell.imageURL{
profileImage.image = UIImage(named: "info")
} else{
self.profileImage.image = UIImage(named: "info")
}
self.teamLbl.text = unwrappedCell.name
mainBackground.backgroundColor = UIColor.gray
}
}
let mainBackground: UIView = {
let v = UIView()
v.layer.cornerRadius = 8
v.layer.masksToBounds = true
return v
}()
var isVoteable: Bool = false
//let shadowView = ShadowView()
let profileImage: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.masksToBounds = true
return imageView
}()
let teamLbl: UILabel = {
let label = UILabel()
label.textColor = UIColor.black
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
return label
}()
let myVoteLbl: UILabel = {
let l = UILabel()
l.text = "Oyum: --"
return l
}()
let voteBtn: UIButton = {
let b = UIButton(type: .custom)
b.setImage(UIImage(named: "info"), for: .normal)
return b
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
func setupViews() {
self.backgroundColor = UIColor.white
self.mainBackground.addSubview(profileImage)
self.mainBackground.addSubview(teamLbl)
self.mainBackground.addSubview(voteBtn)
self.mainBackground.addSubview(myVoteLbl)
//self.addSubview(shadowView)
self.addSubview(mainBackground)
self.backgroundColor = UIColor.clear
profileImage.translatesAutoresizingMaskIntoConstraints = false
teamLbl.translatesAutoresizingMaskIntoConstraints = false
voteBtn.translatesAutoresizingMaskIntoConstraints = false
myVoteLbl.translatesAutoresizingMaskIntoConstraints = false
mainBackground.translatesAutoresizingMaskIntoConstraints = false
//shadowView.translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
// self.setCircularImageView()
mainBackground.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)
// shadowView.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: 0, heightConstant: 0)
profileImage.anchor(nil, left: self.mainBackground.leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 10, bottomConstant: 0, rightConstant: 0, widthConstant: 60, heightConstant: 60)
profileImage.anchorCenterYToSuperview()
teamLbl.anchor(self.mainBackground.topAnchor, left: profileImage.rightAnchor, bottom: nil, right: self.voteBtn.leftAnchor, topConstant: 20, leftConstant: 20, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 40)
myVoteLbl.anchor(nil, left: profileImage.rightAnchor, bottom: self.mainBackground.bottomAnchor, right: nil, topConstant: 0, leftConstant: 20, bottomConstant: 20, rightConstant: 0, widthConstant: 0, heightConstant: 0)
voteBtn.anchor(nil, left: nil, bottom: nil, right: self.mainBackground.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 80, heightConstant: 80)
voteBtn.anchorCenterYToSuperview()
}
func setCircularImageView() {
self.profileImage.layer.cornerRadius = CGFloat(roundf(Float(self.profileImage.frame.size.width / 2.0)))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
EDIT: I add prepareForReuse method regarding to below answer but now cells show just white. Am I doing something wrong?
override func prepareForReuse() {
super.prepareForReuse()
teamLbl.text = ""
myVoteLbl.text = ""
profileImage.image = nil
}
I also make cell_data variable "weak var cell_data: TeamModel? and nil it in prepareForReuse() method but still same leaks are coming.
EDIT 2: Full project;
https://github.com/emreond/TableView-Memory-Leak-Project

First things first.
Inside layoutSubviews you are setting anchores. Why ? It's going to be called each time your cells will be called.
Instead of this, move
// self.setCircularImageView()
mainBackground.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)
// shadowView.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: 0, heightConstant: 0)
profileImage.anchor(nil, left: self.mainBackground.leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 10, bottomConstant: 0, rightConstant: 0, widthConstant: 60, heightConstant: 60)
profileImage.anchorCenterYToSuperview()
teamLbl.anchor(self.mainBackground.topAnchor, left: profileImage.rightAnchor, bottom: nil, right: self.voteBtn.leftAnchor, topConstant: 20, leftConstant: 20, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 40)
myVoteLbl.anchor(nil, left: profileImage.rightAnchor, bottom: self.mainBackground.bottomAnchor, right: nil, topConstant: 0, leftConstant: 20, bottomConstant: 20, rightConstant: 0, widthConstant: 0, heightConstant: 0)
voteBtn.anchor(nil, left: nil, bottom: nil, right: self.mainBackground.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 80, heightConstant: 80)
voteBtn.anchorCenterYToSuperview()
Just bellow setupViews() call.
Next thing is that you didn't implement prepareForReuse(), you need to nil the values which u are setting in cellForRow, willDislayCell` etc.
EDIT
var cell_data: TeamModel? - Marking it as optional doesn't make it weak reference, mark it as weak var and nil it every time cell gets reused.

Related

Dynamic cell size uitableview

I get a json object with different text. I arrange the obtained pieces one by one with the following function addText(content: number.content!, bottomAnchor: anchor!):
func addText(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let shortContentPost: UILabel = {
let label = UILabel()
label.text = content
label.numberOfLines = 0
label.sizeToFit()
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.regular)
let attributedString = NSMutableAttributedString(string: label.text!)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
label.attributedText = attributedString;
return label
}()
contentView.addSubview(shortContentPost)
shortContentPost.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 100)
return shortContentPost.bottomAnchor
}
Everything is output in UITableViewCell, but the automatic height can not be adjusted in any way.
Such a strange output route was chosen in order to get a grid like this. And a similar conclusion is used to combine in one cell text, pictures, links and other.
Previously, I implemented all this through the UICollectionView and calculated the height manually. The way worked, but there was a problem with inaccurate calculation of height and he had to refuse.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
internal func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.item == 0){
let cell = tableView.dequeueReusableCell(withIdentifier: "postId", for: indexPath) as! PostViewTableCell
cell.contentArray = contentArray
cell.homeController = self.navigationController
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
return cell
}
These are tableView functions
TableViewCell:
import UIKit
class PostViewTableCell: UITableViewCell {
var anchor: NSLayoutYAxisAnchor?
var contentArray: [PostContentModel]?{
didSet{
anchor = datePublishPost.bottomAnchor
for number in contentArray! {
if(number.switcher == 1) {
anchor = addTextContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 2){
anchor = addImageContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 3){
anchor = addCaptionFirst(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 4){
anchor = addButtonLink(content: number.content!, link: number.link!, bottomAnchor: anchor!)
}
}
}
}
let authorImage: CustomImageView = {
let image = CustomImageView()
image.backgroundColor = .white
image.image = UIImage(named: "authorImage")
image.layer.masksToBounds = false
image.layer.cornerRadius = 15
image.clipsToBounds = true
return image
}()
let authorNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor(red: 0.51, green: 0.51, blue: 0.51, alpha: 1)
return label
}()
let titlePost: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 24, weight: UIFont.Weight.semibold)
label.numberOfLines = 0
return label
}()
let categoryPost: UILabel = {
let label = UILabel()
label.textColor = UIColor(red: 0.31, green: 0.31, blue: 0.31, alpha: 1)
label.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.semibold)
return label
}()
let datePublishPost: UILabel = {
let label = UILabel ()
label.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.regular)
label.textColor = UIColor(red: 0.52, green: 0.52, blue: 0.52, alpha: 1)
return label
}()
var homeController: UINavigationController?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
override func prepareForReuse() {
super.prepareForReuse()
let subviews = self.subviews
for subview in subviews {
subview.removeFromSuperview()
}
}
func setupView(){
addSubview(authorImage)
addSubview(authorNameLabel)
addSubview(titlePost)
addSubview(categoryPost)
addSubview(datePublishPost)
authorImage.anchor(self.topAnchor, left: self.leftAnchor, bottom: nil, right: nil, topConstant: 20, leftConstant: 15, bottomConstant: 0, rightConstant: 0, widthConstant: 30, heightConstant: 30)
authorNameLabel.anchor(authorImage.topAnchor, left: authorImage.rightAnchor, bottom: nil, right: nil, topConstant: 5, leftConstant: 10, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
titlePost.anchor(authorNameLabel.bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 13, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
categoryPost.anchor(titlePost.bottomAnchor, left: self.leftAnchor, bottom: nil, right: nil, topConstant: 7, leftConstant: 15, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
datePublishPost.anchor(categoryPost.topAnchor, left: categoryPost.rightAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 15, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
}
func addTextContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor{
let shortContentPost: UILabel = {
let label = UILabel()
label.text = content
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.regular)
let attributedString = NSMutableAttributedString(string: label.text!)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
label.attributedText = attributedString;
return label
}()
contentView.addSubview(shortContentPost)
shortContentPost.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return shortContentPost.bottomAnchor
}
func addImageContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let image: CustomImageView = {
let image = CustomImageView()
image.image = UIImage(named: content)
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
return image
}()
let imageURL = "https://brodude.ru/wp-content/uploads/" + content.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
image.loadImageUsingUrlString(urlString: imageURL)
contentView.addSubview(image)
image.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 250)
return image.bottomAnchor
}
func addCaptionFirst(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let Caption: UILabel = {
let lb = UILabel()
lb.text = content
lb.numberOfLines = 0
lb.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.semibold)
return lb
}()
contentView.addSubview(Caption)
Caption.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 30, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return Caption.bottomAnchor
}
func addButtonLink(content: String, link: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let button: LinkButton = {
let bt = LinkButton()
bt.LinkString = link
bt.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.semibold)
bt.setTitle(content, for: UIControlState.normal)
bt.titleLabel?.numberOfLines = 0
bt.contentHorizontalAlignment = .left
bt.setTitleColor(UIColor(red: 0.07, green: 0.32, blue: 0.89, alpha: 1.0), for: UIControlState.normal)
bt.addTarget(self, action: #selector(linkOut), for: .touchUpInside)
return bt
}()
button.sizeToFit()
contentView.addSubview(button)
button.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 20, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return button.bottomAnchor
}
#objc func linkOut(sender: LinkButton!){
let viewController: WebViewController = {
let pv = WebViewController()
pv.LinkString = sender.LinkString
return pv
}()
homeController?.navigationController?.pushViewController(viewController, animated: true)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you have just 3 static cells, I don't think a tableView is really anyhow useful. UITableView is useful performance-wise if there are many cells that can be reused - then the UI does not have to create and hold the whole UI all the time, but just the visible portion of the tableView.
In your case, since all of the content is pushed into one cell, I would recommend simply using UIScrollView.
Or alternatively (in case of large content) use a tableView with a cell per paragraph.
// You have to provide an estimated minimum height of cell.
i.e
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
The cell can resize above this value.

UIButton in custom UIScrollView class not responding to touches

I have a view structure like below;
-->Custom Scroll View
-->Container View
-->View
-->Button
My problem is that I'm adding a target to UIButton but the touch function is not called. I tried to add the target both in custom view class or view controller. However, neither worked.
What I checked;
I checked if all views' isUserInteractionEnabled is set to true and all of them are true.
I also checked if button has a frame (some posts say about UIButton can be seen but you can't touch it because It has no frame).
I checked probably every answer in stack overflow and most of them talk about isUserInteraction and hierarchy and I think they are all correct in my situation.
UIButton not clickable when custom view called
Custom UIView add target not called
How I create custom view and add button inside of it.
class TyreChangeScrollView: UIScrollView {
//E-Mail
let emailTitleLbl = BaseLabel()
let emailBgView = UIView()
let emailLbl = BaseLabel()
public let emailInfoBtn = UIButton()
let contentView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var shouldSetupConstraints = true
override init(frame: CGRect) {
super.init(frame: frame)
updateUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func updateUI(){
addSubview(contentView)
emailTitleLbl.translatesAutoresizingMaskIntoConstraints = false
emailTitleLbl.text = "E-POSTA ADRESİ"
emailTitleLbl.textAlignment = .left
contentView.addSubview(emailTitleLbl)
emailLbl.translatesAutoresizingMaskIntoConstraints = false
emailLbl.text = "hello#hello.com"
emailLbl.textAlignment = .left
emailInfoBtn.setImage(UIImage(named: "info"), for: .normal)
emailInfoBtn.isUserInteractionEnabled = true
emailBgView.backgroundColor = Color.Common.fieldBg
emailBgView.addSubview(emailLbl)
emailBgView.addSubview(emailInfoBtn)
contentView.addSubview(emailBgView)
}
override func updateConstraints() {
if(shouldSetupConstraints) {
// AutoLayout constraints
contentView.translatesAutoresizingMaskIntoConstraints = false
// constrain the scroll view to 8-pts on each side
contentView.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
contentView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0).isActive = true
emailTitleLbl.anchor(phoneLbl.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, topConstant: 10, leftConstant: 20, bottomConstant: 0, rightConstant: 16, widthConstant: 0, heightConstant: 0)
emailLbl.anchor(emailBgView.topAnchor, left: emailBgView.leftAnchor, bottom: emailBgView.bottomAnchor, right: nil, topConstant: 0, leftConstant: 15, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
emailInfoBtn.anchor(emailBgView.topAnchor, left: nil, bottom: emailBgView.bottomAnchor, right: emailBgView.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 10, widthConstant: 0, heightConstant: 0)
emailBgView.anchor(emailTitleLbl.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, topConstant: 10, leftConstant: 20, bottomConstant: 0, rightConstant: 16, widthConstant: 0, heightConstant: 50)
shouldSetupConstraints = false
}
super.updateConstraints()
}
}
How I declare custom class and add target to UIButton.
class TyreChangeViewController: BaseViewController{
let scrollView = TyreChangeScrollView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
designUI()
scrollView.emailInfoBtn.addTarget(self, action: #selector(emailBtnTapped(_:)), for: UIControlEvents.touchUpInside)
scrollView.phoneInfoBtn.addTarget(self, action: #selector(phoneBtnTapped(_:)), for: UIControlEvents.touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
scrollView.anchor(topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: bottomLayoutGuide.topAnchor, right: view.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)
}
func designUI(){
view.backgroundColor = Color.Common.screenBgColor
scrollView.backgroundColor = Color.Common.screenBgWhite
view.addSubview(scrollView)
}
#objc func emailBtnTapped(_ sender: UIButton){
print("hello")
}}
EDIT: If I add a height constraint to the content view, buttons are working but now it isn't scrollable.
contentView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1.0).isActive = true
After adding a constraint refreshing layout is a must
override func viewWillAppear(_ animated: Bool) {
scrollView.anchor(topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: bottomLayoutGuide.topAnchor, right: view.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)
self.view.layoutIfNeeded()
}

Swift Cannot Round the Corners of my UIButton

I've looked at almost every stackoverflow solution and for some odd reason my button will not round the corners. Can someone check and see what im doing wrong?
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(goToMapsButton)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: seperator.topAnchor, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
goToMapsButton.layer.cornerRadius = 0.5 * goToMapsButton.bounds.size.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
Btw, Im doing this all in the viewDidLoad section of the view controller, if that info makes a difference.
Here is the full viewDidLoadClass for reference:` override func viewDidLoad() {
super.viewDidLoad()
let map = MKMapView()
let view1 = UIView()
view1.backgroundColor = .red
let storeAddress = UILabel()
storeAddress.text = "318 Atwood Avenue"
storeAddress.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let storeCity = UILabel()
storeCity.text = "Rainy River"
storeCity.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let seperator = UIView()
seperator.backgroundColor = .lightGray
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(map)
scrollView.addSubview(view1)
scrollView.addSubview(storeAddress)
scrollView.addSubview(storeCity)
scrollView.addSubview(goToMapsButton)
scrollView.addSubview(seperator)
map.anchorToTop(scrollView.topAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor)
map.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.6).isActive = true
_ = storeAddress.anchor(map.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 16, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = storeCity.anchor(storeAddress.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 8, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: nil, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
print(goToMapsButton.frame.width)
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
_ = seperator.anchor(storeCity.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor, topConstant: 8, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 1)
view1.anchorToTop(map.bottomAnchor, left: self.view.leftAnchor, bottom: scrollView.bottomAnchor, right: self.view.rightAnchor)
view1.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.8).isActive = true
}`
Move this code in viewWillLayoutSubviews:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
}
Or create your custom class for button with rounded corners:
class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = true
layer.cornerRadius = cornerRadius
}
}
Give your button a frame. like that
goToMapsButton.frame = CGRect(x: xposition, y:yposition, width: widthyouwant, height: heightyouwant)
as currently your size of button is zero 0*0.5 = zero thats why its not applying any radius.
give it frame and it will work...
UIButton might have a background UIImage with rounded corners. It allowes you to set a background UIImage for each UIControlState of your UIButton.
open class UIButton : UIControl, NSCoding {
open func setBackgroundImage(_ image: UIImage?, for state: UIControlState)
}
If your UIButton's size is determined at runtime and the radius is fixed - you can use a resizable image:
open class UIImage : NSObject, NSSecureCoding {
open func resizableImage(withCapInsets capInsets: UIEdgeInsets) -> UIImage
}
On the gif below I use UIImage with size = CGSize(width: 7, height: 7), corner radius = 3 and cap insets = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)

UIView subviews not appearing

I have a view controller with a UIView. Inside that UIView I add elements like a text label. When I run my code it is not there.
import UIKit
class EventDetailViewController: UIViewController {
//variables that will hold data sent in through previous event controller
var eventImage = ""
var eventName = ""
var eventDescription = ""
var eventStreet = ""
var eventCity = ""
var eventState = ""
var eventZip = 0
var eventDate = ""
//
lazy var currentEventImage : UIImageView = {
let currentEvent = UIImageView()
let imageURL = URL(string: self.eventImage)
currentEvent.af_setImage(withURL: imageURL!)
currentEvent.clipsToBounds = true
currentEvent.translatesAutoresizingMaskIntoConstraints = false
currentEvent.contentMode = .scaleToFill
currentEvent.layer.masksToBounds = true
return currentEvent
}()
//will be responsible for creating the UIView that contains relevant event information
let eventInfoContainerView: UIView = {
let infoContainer = UIView()
infoContainer.backgroundColor = UIColor.white
infoContainer.layer.masksToBounds = true
return infoContainer
}()
lazy var eventNameLabel: UILabel = {
let currentEventName = UILabel()
currentEventName.text = self.eventName
currentEventName.translatesAutoresizingMaskIntoConstraints = false
return currentEventName
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
navigationItem.title = eventName
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: UIImage(named: "icons8-Back-64"), style: .plain, target: self, action: #selector(GoBack))
self.navigationItem.leftBarButtonItem = backButton
//Subviews will be added here
view.addSubview(currentEventImage)
view.addSubview(eventInfoContainerView)
eventInfoContainerView.addSubview(eventNameLabel)
//Constraints will be added here
_ = currentEventImage.anchor(view.centerYAnchor, left: nil, bottom: nil, right: nil, topConstant: -305, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: self.view.frame.width, heightConstant: 200)
_ = eventInfoContainerView.anchor(currentEventImage.bottomAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = eventNameLabel.anchor(eventInfoContainerView.bottomAnchor, left: view.leftAnchor, bottom: nil, right: nil, topConstant: 20, leftConstant: 32, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
}
func GoBack(){
_ = self.navigationController?.popViewController(animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am kind of lost I viewed a tutorial where they added elements to a UIView in a similar fashion and things appeared. Any insight would be helpful. Tried changing multiple things and nothing really made it appear and I have researched a couple answers I know it is probably something small that my eyes are just looking past but I can't discover it at this point.
This is my custom method for setting constraints
import UIKit
extension UIView {
func anchorToTop(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil) {
anchorWithConstantsToTop(top, left: left, bottom: bottom, right: right, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0)
}
func anchorWithConstantsToTop(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: topConstant).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: leftConstant).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -rightConstant).isActive = true
}
}
func anchor(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
translatesAutoresizingMaskIntoConstraints = false
var anchors = [NSLayoutConstraint]()
if let top = top {
anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant))
}
if let left = left {
anchors.append(leftAnchor.constraint(equalTo: left, constant: leftConstant))
}
if let bottom = bottom {
anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant))
}
if let right = right {
anchors.append(rightAnchor.constraint(equalTo: right, constant: -rightConstant))
}
if widthConstant > 0 {
anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
}
if heightConstant > 0 {
anchors.append(heightAnchor.constraint(equalToConstant: heightConstant))
}
anchors.forEach({$0.isActive = true})
return anchors
}
}
OK - now that we see your .anchor(...) function...
The TOP of your label is set to the BOTTOM of eventInfoContainerView with topConstant: 20 ... which is +20
Change that value to -20 (assuming you want the label along the bottom of the view):
_ = eventNameLabel.anchor(eventInfoContainerView.bottomAnchor, left: view.leftAnchor, bottom: nil, right: nil, topConstant: -20, leftConstant: 32, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
Assuming your .anchor(...) function / extension is working properly, I think the only thing wrong is that you forgot to set:
infoContainer.translatesAutoresizingMaskIntoConstraints = false
when you initialize your eventInfoContainerView

Make UITableView's height depended on it's cells' height

I have got a table in my Swift project
tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableViewAutomaticDimension
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let ProfilePicture = UIImageView()
ProfilePicture.image = #imageLiteral(resourceName: "cole")
ProfilePicture.layer.cornerRadius = 25
ProfilePicture.layer.masksToBounds = true
let username = UILabel()
username.text = posters[indexPath.row]
username.textColor = UIColor(r: 68, g: 68, b: 85)
username.font = UIFont(name: "AppleSDGothicNeo-Light", size: 18)
username.numberOfLines = 0
let postText = UITextView()
postText.text = postTexts[indexPath.row]
postText.textColor = UIColor(r: 83, g: 92, b: 87)
postText.font = UIFont(name: "Courier", size: 15)
postText.isScrollEnabled = false
postText.isUserInteractionEnabled = false
let border = UIView()
border.backgroundColor = UIColor(r: 17, g: 221, b: 219)
cell.addSubview(ProfilePicture)
cell.addSubview(username)
cell.addSubview(postText)
cell.addSubview(border)
ProfilePicture.anchor(cell.topAnchor, left:cell.leftAnchor, bottom: nil, right: nil, topConstant: 15, leftConstant: 14, bottomConstant: 0, rightConstant: 0, widthConstant: 50,heightConstant:50)
username.anchor(cell.topAnchor, left:ProfilePicture.rightAnchor, bottom: postText.topAnchor, right: cell.rightAnchor, topConstant: 20, leftConstant: 13, bottomConstant: 0, rightConstant: 15, widthConstant: 0,heightConstant:20)
postText.anchor(username.bottomAnchor, left:ProfilePicture.rightAnchor, bottom:nil, right: cell.rightAnchor, topConstant: 0, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0)
border.anchor(postText.bottomAnchor, left:cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, topConstant: 20, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0,heightConstant:1)
cell.layoutIfNeeded()
cell.sizeToFit()
return cell
}
Cell's get automatic height,but tableView's height is not automatic how can i make it automatic? I am not using storyboards only programmatical approach.
You should use AutoLayout and make sure all components anchor to top and bottom of the contentView, like this:
You can make dynamic cell which will be dependent on label's height using
tableView.rowHeight = UITableViewAutomaticDimension // in your viewDidLoad
but make sure that you are not giving fix height and width constraint to your label.
you should set numberOfLines = 0. With this your label will have multiline text.

Resources