Related
I have a horizontal UICollectionView (with paging) where each cell has a fullscreen UITableView. The parent ViewController has a navigation bar with large titles where an animation occurs during vertical scrolling. However, since my table views are within the horizontal collection view, the large navigation bar does not animate when scrolling.
I have attempted a solution of setting a delegate for the tableview scrolling and then set the y offset of the collection view but the result glitches and does not work correctly.
Here is my current code (without TableView/CollectionView delegate/datasource methods)
class HomeViewController: UIViewController, HomeCellDelegate {
var delegate: HomeViewControllerDelegate!
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
create()
}
func create() {
view.backgroundColor = .background
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.sectionInset = .zero
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(HomeCell.self, forCellWithReuseIdentifier: "cellID")
}
//Delegate from UICollectionView cell (the table view)
func tableViewDidScroll(_ contentOffset: CGPoint) {
collectionView.contentOffset.y = contentOffset.y //Glitches
}
}
protocol HomeCellDelegate {
func tableViewDidScroll(_ contentOffset: CGPoint)
}
class HomeCell: UICollectionViewCell {
var events = [String]()
let tableView = UITableView()
var delegate: HomeCellDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
create()
}
func create() {
backgroundColor = .clear
addSubview(tableView)
tableView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
tableView.contentInsetAdjustmentBehavior = .never
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = CGPoint(x: 0.0, y: scrollView.contentOffset.y + scrollView.adjustedContentInset.top)
delegate.tableViewDidScroll(offset)
}
required init?(coder: NSCoder) {
fatalError()
}
}
I'm currently working on a side menu for my app, everything is working fine, however I've run into a problem the past week that I haven't been able to figure out how to solve.
The problem is that when the table view loads, the cells are pushed up and somewhat overlaps with the status bar, see picture 1 please.
Ideally I'm trying to achieve what picture 2 shows.
From what I've diagnosed, it has something to do with calling the Table View on viewDidLoad, because when I make a selection from the side menu and then dismiss it, the side menu cells are aligned correctly (see picture 2).
I'm not exactly sure what to make of the issue because the cells don't align correctly on initial load up of the app.
func configureTableView() {
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(MenuOptionCell.self, forCellReuseIdentifier: reuseIdentifier)
tableView.backgroundColor = .lightGray
tableView.separatorStyle = .none
tableView.rowHeight = 80
view.addSubview(tableView)
tableView.anchorwithConstant(top: view.topAnchor, bottom: view.bottomAnchor, leading: view.leadingAnchor, trailing: view.trailingAnchor, paddingTop: 0, paddingBottom: 0, paddingLeading: 0, paddingTrailing: 80, width: 0, height: 0)
}
In the screenshots you shared, there is overlapping at top and bottom both. In iOS 11.0 and onwards safeAreaLayoutGuide represents the portion of your view that is unobscured by bars and other content. So adding top and bottom constraints with topLayoutGuide and bottomLayoutGuide resolves issue of overlapping.
Here is how you can add these constraints in Storyboard/Xib.
Use safeAreaLayoutGuide during programmatic constraint. Maybe it solves your problem
func configureTableView() {
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(MenuOptionCell.self, forCellReuseIdentifier: reuseIdentifier)
tableView.backgroundColor = .lightGray
tableView.separatorStyle = .none
tableView.rowHeight = 80
view.addSubview(tableView)
tableView.anchorwithConstant(top: view.safeAreaLayoutGuide.topAnchor, bottom: view.bottomAnchor, leading: view.leadingAnchor, trailing: view.trailingAnchor, paddingTop: 0, paddingBottom: 0, paddingLeading: 0, paddingTrailing: 80, width: 0, height: 0)
}
I think you can use heightForHeaderInSection to increase the margin at the top of the table view. For example:
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100.0
}
I have following suggestion. Please try this
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
}
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?
Devices I test it;
iPhone X on 11.3.1
iPhone 6 11.2.5
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
EDIT 3: I found that when I add If check and change attributes inside didst of cell_data, some cells views look ruined. For example when I change teamLbl text color inside cell_data. In some cells, It looks ruined.
EDIT 4: When I comment anchor codes to check with reference of #Darp's answer,(I don't have anything seen on screen and don't have any constraint) It still continue to give leak.
EDIT 5: I'm still working on this leak but can't find any solution. Actually when I look what is in memory when tableView is on screen, I see that only 8 cell is on memory which looks like correct.
I have the same problem with _NSContiguousString. Even in an empty application with just one label, it shows a leak of 48 bytes.
I am not sure, but I have a guess that the reason for leaking might be bridging with NSString (I see also [_SwiftNativeNSStringBase retain] on the top of the stack trace).
I've found out that NSString itself might be considered as a leaking object by the Instruments because it has been implemented as a Singleton.
My own question is here: Memory leaks when assigning text to UILabel (iOS, Swift 4, XCode9)
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.
I have a collection view with a Header and no cells.
I've set the header size to be equal to the superview bounds as following:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
This header has a UITextView in it, which is anchored, as well, to the headers bounds (topAnchor, leftAnchor, bottomAnchor and rightAnchor).
I need the header to be anchored to the collection view height when the keyboard is closed, but anchored to the top of the keyboard when this one is opened, changing its size respectively.
The problem here lies when the text of the headers UITextView subview keeps growing and the text goes behind the keyboard.
Here's a GIF showing what's happening.
The yellow view is the Header
The white view is the TextView with some padding.
When I pass the keyboard the text goes behind it - if I could anchor the header to the bottom of the screen when there's no keyboard and to the bottom of the keyboard when its shown would be great.
Any hint?
Thanks
EDIT ADICIONAL INFO
As #Aravind said I've implemented the Notification listener to fire whenever the keyboard shows or hides.
Since I'm using the UITextView inside the Header class as its subview I've implemented the following:
This is an helper function to anchor my views easily:
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddinfLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddinfLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(inputTextView)
inputTextView.delegate = self
inputTextView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddinfLeft: 12, paddingBottom: 0, paddingRight: 12, width: 0, height: 0)
setupInputTextViewToolbar()
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
}
Then the Show/Hide functions
func keyboardWillShow(_ sender:NSNotification){
guard let keyboardSize = (sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue else { return }
inputTextView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddinfLeft: 12, paddingBottom: keyboardSize.height, paddingRight: 12, width: 0, height: 0)
}
func keyboardWillHide(_ sender:NSNotification){
self.inputTextView.anchor(top: self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, paddingTop: 0, paddinfLeft: 12, paddingBottom: 0, paddingRight: 12, width: 0, height: 0)
}
The show function works properly and makes the text view to the remaining screen size so it doesn't stay behind the keyboard, so far so good.
But when I fire the hide function it doesn't resize again to the bottom of the screen.
What maybe causing this?
Here make an outlet of the textView's bottom constraint and change its value when keyboard shows up and when it hides like,
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
}
func keyboardWillShow(_ sender:NSNotification){
let keyboardSize = (sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
bottomSpace.constant = (keyboardSize?.height)!
}
func keyboardWillHide(_ sender:NSNotification){
bottomSpace.constant = 0
}
I have got this table in my swift project
tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
let tableViewHeight = postTexts.count*200
tableView.estimatedRowHeight = 55
tableView.rowHeight = UITableViewAutomaticDimension
tableView.allowsSelection = false;
tableView.reloadData()
tableView.layoutIfNeeded()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat{
return 55
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let ProfilePicture = UIImageView()
ProfilePicture.image = #imageLiteral(resourceName: "cole")
let username = UILabel()
username.text = posters[indexPath.row]
username.numberOfLines = 0
let postText = UITextView()
postText.text = postTexts[indexPath.row]
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)
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: 20, rightConstant: 0, widthConstant: 0,heightConstant:1)
cell.layoutIfNeeded()
cell.sizeToFit()
return cell
}
I want my table to show all cells,but it only shows half of them like here
As you see he only shows 2 cells even though there are 4 of them,how can i change it's behaviour to show all cells?
Here are my table constraints
tableView.anchor(PostsDiv.bottomAnchor, left:view.leftAnchor, bottom: nil, right: view.rightAnchor, topConstant: 40, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0,heightConstant:tableView.contentSize.height+tableView.contentInset.bottom+tableView.contentInset.top)
This is still the same issue from your older posts. Your cells aren't working correctly since you want automatic height in cells but you have the wrong constraints set on your tableView and cell. This is a third repost of your question but here is your answer from Andrea and me:
Swift UITableViewAutomaticDimension is not working
I created nearly the same UI using interface builder.
https://github.com/Hapeki/Example4
I recommend you take a look at this and how AutoLayout works before settings stuff programmatically. The UI is based on my explanation from https://stackoverflow.com/a/42971009/6414904
Demo video:
The cells are automatically sized, seen by the large lorem ipsum text. If you want to make the entire screen scroll you should consider making a headerView for your tableView, but that's out of the scope for this question. In your code you set the tableView height based on the count of your cells * height: let tableViewHeight = postTexts.count*200
, thats is the wrong way to work and it will cause issues after issues. I would highly recommend using interface builder (Storyboards) or if you want some "hybrid" approach you could go for xibs/nibs, where you can make View classes that contain outlets and do some programmatic stuff in it.