dynamic UITableViewCell height programmatically - ios

I am trying to create a tableview cell programmatically with varying heights.
// Created by AJ Norton on 7/29/15.
// Copyright (c) 2015 AJ Norton. All rights reserved.
import UIKit
class ViewController: UITableViewController {
var arr = ["image1.jpg", "image2.jpg"]
override func viewDidLoad() {
super.viewDidLoad()
self.title = "he"
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
self.tableView.registerClass(CustCellTableViewCell.self, forCellReuseIdentifier: "cell")
// Do any additional setup after loading the view, typically from a nib.
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! CustCellTableViewCell
cell.label.text = arr[indexPath.row]
cell.imageContainer.image = UIImage(named: arr[indexPath.row])
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var i = UIImage(named: arr[indexPath.row])!
return CGFloat(i.size.height + 15.0)
}
}
this is my Custom cell:
// Created by AJ Norton on 7/29/15.
// Copyright (c) 2015 AJ Norton. All rights reserved.
import UIKit
class CustCellTableViewCell: UITableViewCell {
lazy var imageContainer: UIImageView = {
var i = UIImageView()
i.setTranslatesAutoresizingMaskIntoConstraints(false)
return i
}()
lazy var label: UILabel = {
var l = UILabel()
l.setTranslatesAutoresizingMaskIntoConstraints(false)
l.textColor = UIColor.redColor()
return l
}()
override func layoutSubviews() {
self.addSubview(label)
self.addSubview(imageContainer)
self.backgroundColor = UIColor.greenColor()
var viewDict = ["label": label, "image": imageContainer]
var constraint1 = NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[image]-5-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewDict)
var constraint2 = NSLayoutConstraint.constraintsWithVisualFormat("V:|[label]-[image]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewDict)
var constraint3 = NSLayoutConstraint.constraintsWithVisualFormat("H:|[label]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewDict)
self.addConstraints(constraint1)
self.addConstraints(constraint2)
self.addConstraints(constraint3)
self.sizeToFit()
}
}
When I run the app I get this: . I want the images to take up their maximum height (300px) for the cell (Also the whole background should be green.

just delete self.sizeToFit() and it works.

Related

Creating a TableView Programmatically with multiple cell in swift

As I am creating a TableView programmatically with multiple cell and in each cell having UICollectionView, UITableView, UITableView.... but I am not able to find the error and every time when I run the program it Shows
" Command failed due to signal: Segmentation fault: 11".
Has any one created this type of UI using coding.
New in Swift so forgive for errors.
// ViewController.swift
// Json Parsing in Swift
//
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UITableViewController {
var dataArray = Array<JSON>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "http://104.131.162.14:3033/api/ios/detail").validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
print("JSON: \(json)")
var trafficJson = json["traffic_partners"]
trafficJson["type"] = "Traffic"
self.dataArray.append(trafficJson)
var newsJson = json["news"]
newsJson["type"] = "News"
self.dataArray.append(newsJson)
var categoryJson = json["category"]
categoryJson["type"] = "Category"
self.dataArray.append(categoryJson)
var topFreeApps = json["top_free_apps"]
topFreeApps["type"] = "TopApps"
self.dataArray.append(topFreeApps)
var topSites = json["top_sites"]
topSites["type"] = "TopSites"
self.dataArray.append(topSites)
var trendingVideos = json["tranding_video"]
trendingVideos["type"] = "TrendingVideos"
self.dataArray.append(trendingVideos)
var sports = json["sports"]
sports["type"] = "Sports"
self.dataArray.append(sports)
var jokes = json["jokes"]
jokes["type"] = "jokes"
self.dataArray.append(jokes)
print(self.dataArray[0]["detail"][0].object)
print(self.dataArray[2]["detail"].object)
self.tableView.reloadData()
}
case .Failure(let error):
print(error)
}
}
tableView.registerClass(MyCell.self, forCellReuseIdentifier: "cellId")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! MyCell
myCell.nameLabel.text = dataArray[indexPath.row]["type"].string
if (dataArray[indexPath.row]["type"].string == "News") {
myCell.newsArray = dataArray[indexPath.row]["detail"].arrayObject
}
myCell.myTableViewController = self
return myCell
}
}
class MyCell: UITableViewCell {
var newsArray :NSMutableArray=[]
var myTableViewController: ViewController?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let newsTableView: UITableView = {
let newsTV = UITableView(frame:UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
newsTV.registerClass(NewsTableViewCell.self, forCellReuseIdentifier: "NewsTableViewCell")
return newsTV
}()
func tableView(newsTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArray.count
}
func tableView(newsTableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = newsTableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! NewsTableViewCell
myCell.nameLabel.text = newsArray[indexPath.row]["title"].string
myCell.myTableViewController = myTableViewController
return myCell
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Sample Item"
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFontOfSize(14)
return label
}()
func setupViews() {
addSubview(newsTableView)
addSubview(nameLabel)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": newsTableView]))
}
func handleAction() {
}
}

How do I replicate iOS 10's Apple Music "Peek and pop action menu"

iOS 10 has a feature I would like to replicate. When you 3D touch an album in the Apple Music app it opens the menu shown below. However unlike a normal peek and pop, it does not go away when you raise you finger. How do I replicate this?
The closest I got to replicating it is the following code.. It create a dummy-replica of the Music application.. Then I added the PeekPop-3D-Touch delegates.
However, in the delegate, I add an observer to the gesture recognizer and then cancel the gesture upon peeking but then re-enable it when the finger is lifted. To re-enable it, I did it async because the preview will disappear immediately without the async dispatch. I couldn't find a way around it..
Now if you tap outside the blue box, it will disappear like normal =]
http://i.imgur.com/073M2Ku.jpg
http://i.imgur.com/XkwUBly.jpg
//
// ViewController.swift
// PeekPopExample
//
// Created by Brandon Anthony on 2016-07-16.
// Copyright © 2016 XIO. All rights reserved.
//
import UIKit
class MusicViewController: UITabBarController, UITabBarControllerDelegate {
var tableView: UITableView!
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.initControllers()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initControllers() {
let libraryController = LibraryViewController()
let forYouController = UIViewController()
let browseController = UIViewController()
let radioController = UIViewController()
let searchController = UIViewController()
libraryController.title = "Library"
libraryController.tabBarItem.image = nil
forYouController.title = "For You"
forYouController.tabBarItem.image = nil
browseController.title = "Browse"
browseController.tabBarItem.image = nil
radioController.title = "Radio"
radioController.tabBarItem.image = nil
searchController.title = "Search"
searchController.tabBarItem.image = nil
self.viewControllers = [libraryController, forYouController, browseController, radioController, searchController];
}
}
And the implementation of ForceTouch pausing..
//
// LibraryViewController.swift
// PeekPopExample
//
// Created by Brandon Anthony on 2016-07-16.
// Copyright © 2016 XIO. All rights reserved.
//
import Foundation
import UIKit
//Views and Cells..
class AlbumView : UIView {
var albumCover: UIImageView!
var title: UILabel!
var artist: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initControls() {
self.albumCover = UIImageView()
self.title = UILabel()
self.artist = UILabel()
}
func setTheme() {
self.albumCover.contentMode = .scaleAspectFit
self.albumCover.layer.cornerRadius = 5.0
self.albumCover.backgroundColor = UIColor.lightGray()
self.title.text = "Unknown"
self.title.font = UIFont.systemFont(ofSize: 12)
self.artist.text = "Unknown"
self.artist.textColor = UIColor.lightGray()
self.artist.font = UIFont.systemFont(ofSize: 12)
}
func doLayout() {
self.addSubview(self.albumCover)
self.addSubview(self.title)
self.addSubview(self.artist)
let views = ["albumCover": self.albumCover, "title": self.title, "artist": self.artist];
var constraints = Array<String>()
constraints.append("H:|-0-[albumCover]-0-|")
constraints.append("H:|-0-[title]-0-|")
constraints.append("H:|-0-[artist]-0-|")
constraints.append("V:|-0-[albumCover]-[title]-[artist]-0-|")
let aspectRatioConstraint = NSLayoutConstraint(item: self.albumCover, attribute: .width, relatedBy: .equal, toItem: self.albumCover, attribute: .height, multiplier: 1.0, constant: 0.0)
self.addConstraint(aspectRatioConstraint)
for constraint in constraints {
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
class AlbumCell : UITableViewCell {
var firstAlbumView: AlbumView!
var secondAlbumView: AlbumView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initControls() {
self.firstAlbumView = AlbumView(frame: CGRect.zero)
self.secondAlbumView = AlbumView(frame: CGRect.zero)
}
func setTheme() {
}
func doLayout() {
self.contentView.addSubview(self.firstAlbumView)
self.contentView.addSubview(self.secondAlbumView)
let views: [String: AnyObject] = ["firstAlbumView": self.firstAlbumView, "secondAlbumView": self.secondAlbumView];
var constraints = Array<String>()
constraints.append("H:|-15-[firstAlbumView(==secondAlbumView)]-15-[secondAlbumView(==firstAlbumView)]-15-|")
constraints.append("V:|-15-[firstAlbumView]-15-|")
constraints.append("V:|-15-[secondAlbumView]-15-|")
for constraint in constraints {
self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.contentView.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
//Details..
class DetailSongViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blue()
}
/*override func previewActionItems() -> [UIPreviewActionItem] {
let regularAction = UIPreviewAction(title: "Regular", style: .default) { (action: UIPreviewAction, vc: UIViewController) -> Void in
}
let destructiveAction = UIPreviewAction(title: "Destructive", style: .destructive) { (action: UIPreviewAction, vc: UIViewController) -> Void in
}
let actionGroup = UIPreviewActionGroup(title: "Group...", style: .default, actions: [regularAction, destructiveAction])
return [actionGroup]
}*/
}
//Implementation..
extension LibraryViewController : UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = self.tableView.indexPathForRow(at: location) else {
return nil
}
guard let cell = self.tableView.cellForRow(at: indexPath) else {
return nil
}
previewingContext.previewingGestureRecognizerForFailureRelationship.addObserver(self, forKeyPath: "state", options: .new, context: nil)
let detailViewController = DetailSongViewController()
detailViewController.preferredContentSize = CGSize(width: 0.0, height: 300.0)
previewingContext.sourceRect = cell.frame
return detailViewController
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
//self.show(viewControllerToCommit, sender: self)
}
override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer<Void>?) {
if let object = object {
if keyPath == "state" {
let newValue = change![NSKeyValueChangeKey.newKey]!.integerValue
let state = UIGestureRecognizerState(rawValue: newValue!)!
switch state {
case .began, .changed:
self.navigationItem.title = "Peeking"
(object as! UIGestureRecognizer).isEnabled = false
case .ended, .failed, .cancelled:
self.navigationItem.title = "Not committed"
object.removeObserver(self, forKeyPath: "state")
DispatchQueue.main.async(execute: {
(object as! UIGestureRecognizer).isEnabled = true
})
case .possible:
break
}
}
}
}
}
class LibraryViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.initControls()
self.setTheme()
self.registerClasses()
self.registerPeekPopPreviews();
self.doLayout()
}
func initControls() {
self.tableView = UITableView(frame: CGRect.zero, style: .grouped)
}
func setTheme() {
self.edgesForExtendedLayout = UIRectEdge()
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
func registerClasses() {
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Default")
self.tableView.register(AlbumCell.self, forCellReuseIdentifier: "AlbumCell")
}
func registerPeekPopPreviews() {
//if (self.traitCollection.forceTouchCapability == .available) {
self.registerForPreviewing(with: self, sourceView: self.tableView)
//}
}
func doLayout() {
self.view.addSubview(self.tableView)
let views: [String: AnyObject] = ["tableView": self.tableView];
var constraints = Array<String>()
constraints.append("H:|-0-[tableView]-0-|")
constraints.append("V:|-0-[tableView]-0-|")
for constraint in constraints {
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.view.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 5 : 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (indexPath as NSIndexPath).section == 0 ? 44.0 : 235.0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 75.0 : 50.0
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.0001
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return section == 0 ? "Library" : "Recently Added"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).section == 0 { //Library
let cell = tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath)
switch (indexPath as NSIndexPath).row {
case 0:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Playlists"
case 1:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Artists"
case 2:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Albums"
case 3:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Songs"
case 4:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Downloads"
default:
break
}
}
if (indexPath as NSIndexPath).section == 1 { //Recently Added
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumCell", for: indexPath)
cell.selectionStyle = .none
return cell
}
return tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath)
}
}
This actually might be done using UIPreviewInteraction API.
https://developer.apple.com/documentation/uikit/uipreviewinteraction
It is almost similar to the Peek and Pop API.
Here we have 2 phases: Preview and Commit which are corresponding to the Peek and Pop in the later API. we have UIPreviewInteractionDelegate which gives us the access to the transition through these phases.
So what one should do is, to replicate the above Apple Music Popup,
Manually show a blur overlay during didUpdatePreviewTransition
Build an xib of the above menu and show it during didUpdateCommitTransition
You can make the view stay there on commitTransition phase end.
Actually, apple has built a demo of this in the form of a Chat App.
Download the sample code from here and test it out.
I wrote some code to replicate like apple music style peek and pop.
Work like below
Explanation
TopView.xib, TopView.swift (You can customize it)
PeekAndPopActionView.swift (View for single action, such as download, share ..)
PeekAndPopController.swift (Present, Dismiss the view)
ForceTouchGestureRecognizer.swift (Detect Force Touch)
Usage
fileprivate let peekedViewController = PeekAndPopController()
#IBAction func presentAction(_ sender: Any) {
present(peekedViewController, animated: true)
}
let forceTouch = ForceTouchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
forceTouch.addTarget(self, action: #selector(touchAction(_:)))
forceTouch.cancelsTouchesInView = false
view.addGestureRecognizer(forceTouch)
let download = PeekAndPopActionView(text: "Download", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Download Action")
})
let playNext = PeekAndPopActionView(text: "Play Next", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Play Next Action")
})
let playLast = PeekAndPopActionView(text: "Play Later", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Play Last Action")
})
let share = PeekAndPopActionView(text: "Share", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Share Action")
})
peekedViewController.addAction(download)
peekedViewController.addAction(playNext)
peekedViewController.addAction(playLast)
peekedViewController.addAction(share)
peekedViewController.topView = TopView().loadNib()
peekedViewController.topView?.handler = {
print("Play Play Play")
}
}
#objc func touchAction(_ gesture: ForceTouchGestureRecognizer) {
print(#function, gesture.touch?.location(in: view) ?? "")
present(peekedViewController, animated: true)
}

Dynamic UITableView row height using UIStackView?

Surprised this isn't working out of the box, as this seems to be an important use case for stack views. I have a UITableViewCell subclass which adds a UIStackView to the contentView. I'm adding labels to the stack view in tableView(_cellForRowAtIndexPath:) and the tableview is set to use dynamic row heights, but it doesn't appear to work, at least in Xcode 7.3. I was also under the impression that hiding arranged subviews in a stack view was animatable, but that seems broken as well.
Any ideas on how to get this working correctly?
class StackCell : UITableViewCell {
enum VisualFormat: String {
case HorizontalStackViewFormat = "H:|[stackView]|"
case VerticalStackViewFormat = "V:|[stackView(>=44)]|"
}
var hasSetupConstraints = false
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = .Vertical
stack.distribution = .FillProportionally
stack.alignment = .Fill
stack.spacing = 3.0
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
if !hasSetupConstraints {
hasSetupConstraints = true
let viewsDictionary: [String:AnyObject] = ["stackView" : stackView]
var newConstraints = [NSLayoutConstraint]()
newConstraints += self.newConstraints(VisualFormat.HorizontalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
newConstraints += self.newConstraints(VisualFormat.VerticalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
addConstraints(newConstraints)
}
super.updateConstraints()
}
private func newConstraints(visualFormat: String, viewsDictionary: [String:AnyObject]) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: nil, views: viewsDictionary)
}
class ViewController: UITableViewController {
private let reuseIdentifier = "StackCell"
private let cellClass = StackCell.self
override func viewDidLoad() {
super.viewDidLoad()
configureTableView(self.tableView)
}
private func configureTableView(tableView: UITableView) {
tableView.registerClass(cellClass, forCellReuseIdentifier: reuseIdentifier)
tableView.separatorStyle = .SingleLine
tableView.estimatedRowHeight = 88
tableView.rowHeight = UITableViewAutomaticDimension
}
private func newLabel(title: String) -> UILabel {
let label = UILabel()
label.text = title
return label
}
// MARK: - UITableView
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 4
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44.0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StackCell
cell.stackView.arrangedSubviews.forEach({$0.removeFromSuperview()})
cell.stackView.addArrangedSubview(newLabel("\(indexPath.section)-\(indexPath.row)"))
cell.stackView.addArrangedSubview(newLabel("Second Label"))
cell.stackView.addArrangedSubview(newLabel("Third Label"))
cell.stackView.addArrangedSubview(newLabel("Fourth Label"))
cell.stackView.addArrangedSubview(newLabel("Fifth Label"))
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! StackCell
for (idx, view) in cell.stackView.arrangedSubviews.enumerate() {
if idx == 0 {
continue
}
view.hidden = !view.hidden
}
UIView.animateWithDuration(0.3, animations: {
cell.contentView.layoutIfNeeded()
tableView.beginUpdates()
tableView.endUpdates()
})
}
}
It seems that for this to work the constraints need to be added in the init of the UITableViewCell and added to the contentView instead of cell's view.
The working code looks like this:
import UIKit
class StackCell : UITableViewCell {
enum VisualFormat: String {
case HorizontalStackViewFormat = "H:|[stackView]|"
case VerticalStackViewFormat = "V:|[stackView(>=44)]|"
}
var hasSetupConstraints = false
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = UILayoutConstraintAxis.Vertical
stack.distribution = .FillProportionally
stack.alignment = .Fill
stack.spacing = 3.0
stack.translatesAutoresizingMaskIntoConstraints = false
stack.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
addStackConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addStackConstraints() {
let viewsDictionary: [String:AnyObject] = ["stackView" : stackView]
var newConstraints = [NSLayoutConstraint]()
newConstraints += self.newConstraints(VisualFormat.HorizontalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
newConstraints += self.newConstraints(VisualFormat.VerticalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
contentView.addConstraints(newConstraints)
super.updateConstraints()
}
private func newConstraints(visualFormat: String, viewsDictionary: [String:AnyObject]) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: nil, views: viewsDictionary)
}
}

UITableViewCell selection in editing mode.

I am making a custom UIViewController that I want to show a message when a UITableViewCell is tapped. I create a UITableView and set the property
tableView.allowsSelectionDuringEditing = true
Even though that property has been set to true, tableView(:didSelectRowAtIndexPath) is not being called to display the message. Why is this and how do I fix it?
Here is my UITableViewController:
class GameListViewController: UIViewController, UITableViewDataSource, GameListViewDelegate, UITableViewDelegate
{
private var _games: [GameObject] = []
var tableView: UITableView {return (view as GameListView).tableView}
var gameListView: GameListView {return (view as GameListView) }
override func loadView()
{
view = GameListView(frame: UIScreen.mainScreen().bounds)
gameListView.delegate = self
}
override func viewDidLoad()
{
super.viewDidLoad()
tableView.dataSource = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return _games.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var index: Int = indexPath.row
let currentGame = _games[index]
var cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: nil)
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByCharWrapping
cell.textLabel?.numberOfLines = 2
if currentGame.isFinished == true
{
cell.imageView?.image = UIImage(named: "Finished.png")
cell.textLabel?.text = "Winner: Player\(currentGame.playerMakingMove)\nMissiles Launched: \(currentGame.missileCount)"
}
else
{
cell.imageView?.image = UIImage(named: "Resume.png")
cell.textLabel?.text = "Turn: Player\(currentGame.playerMakingMove)\nMissiles Launched: \(currentGame.missileCount)"
}
return cell
}
/*Handles deleting the cells*/
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if (editingStyle == UITableViewCellEditingStyle.Delete)
{
var index: Int = indexPath.row
_games.removeAtIndex(index)
tableView.deleteRowsAtIndexPaths(NSArray(array: [indexPath]), withRowAnimation: UITableViewRowAnimation.Left)
}
tableView.reloadData()
}
/* Performs when a cell is touched */
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
var index: Int = indexPath.row
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
println("inside tableView: didSelectRowAtIndexPath")
var message = UIAlertView(title: "Row selected", message: "You have selected a row", delegate: nil, cancelButtonTitle: "Click ME!", otherButtonTitles: "no other buttons")
message.show()
}
func makeNewGame()
{
addGames(1)
tableView.reloadData()
}
func addGames(gameNumber: Int)
{
var p1 = Player()
var p2 = Player()
for var i = 0; i < gameNumber; i++
{
var randBool = Bool( round(drand48())) // True/False
var randPlayer:Int = Int( round( (drand48() + 1)) ) // 1 or 2
var randMissleCount:Int = Int( arc4random_uniform(5000) + 1 ) //1 - 5001
_games.append(GameObject(isFinished: randBool, playerMakingMove: randPlayer, missileCount: randMissleCount, player1: p1, player2: p2))
}
}
}
Here is my UIView that contains the UITableView:
protocol GameListViewDelegate: class
{
func makeNewGame()
}
class GameListView: UIView, PictureButtonDelegate
{
var newGameButton: PictureButton!
var tableView: UITableView!
weak var delegate: GameListViewDelegate? = nil
override init(frame: CGRect)
{
super.init(frame: frame)
newGameButton = PictureButton(frame: CGRect(), fileName: "NewGame.png")
newGameButton.backgroundColor = UIColor.grayColor()
newGameButton.delegate = self
newGameButton.setTranslatesAutoresizingMaskIntoConstraints(false)
tableView = UITableView()
tableView.allowsSelectionDuringEditing = true
tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(newGameButton)
self.addSubview(tableView)
}
override func layoutSubviews()
{
let views: [String : UIView] = ["button": newGameButton, "tableView": tableView]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[button]-|", options: .allZeros, metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[tableView]-|", options: .allZeros, metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(>=15,<=25)-[button]-[tableView]-|", options: .allZeros, metrics: nil, views: views))
}
func buttonPushed()
{
delegate?.makeNewGame()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You have to set a delegate for the UITableView and implement func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath).
You only set the dataSource, but never the delegate (only a GameListViewDelegate for GameListView).
Change your viewDidLoad function in GameListViewController to:
override func viewDidLoad()
{
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}

Custom UITableViewCell programmatically using Swift

Hey all I am trying to create a custom UITableViewCell, but I see nothing on the simulator. Can you help me please.
I can see the label only if I var labUserName = UILabel(frame: CGRectMake(0.0, 0.0, 130, 30));
but it overlaps the cell. I don't understand, Auto Layout should know the preferred size/minimum size of each cell?
Thanks
import Foundation
import UIKit
class TableCellMessages: UITableViewCell {
var imgUser = UIImageView();
var labUserName = UILabel();
var labMessage = UILabel();
var labTime = UILabel();
override init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
imgUser.layer.cornerRadius = imgUser.frame.size.width / 2;
imgUser.clipsToBounds = true;
contentView.addSubview(imgUser)
contentView.addSubview(labUserName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
//Set layout
var viewsDict = Dictionary <String, UIView>()
viewsDict["image"] = imgUser;
viewsDict["username"] = labUserName;
viewsDict["message"] = labMessage;
viewsDict["time"] = labTime;
//Image
//contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[image(100)]-'", options: nil, metrics: nil, views: viewsDict));
//contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(100)]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-|", options: nil, metrics: nil, views: viewsDict));
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Let's make a few assumptions:
You have an iOS8 project with a Storyboard that contains a single UITableViewController. Its tableView has a unique prototype UITableViewCell with custom style and identifier: "cell".
The UITableViewController will be linked to Class TableViewController, the cell will be linked to Class CustomTableViewCell.
You will then be able to set the following code (updated for Swift 2):
CustomTableViewCell.swift:
import UIKit
class CustomTableViewCell: UITableViewCell {
let imgUser = UIImageView()
let labUserName = UILabel()
let labMessage = UILabel()
let labTime = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
imgUser.backgroundColor = UIColor.blueColor()
imgUser.translatesAutoresizingMaskIntoConstraints = false
labUserName.translatesAutoresizingMaskIntoConstraints = false
labMessage.translatesAutoresizingMaskIntoConstraints = false
labTime.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgUser)
contentView.addSubview(labUserName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
let viewsDict = [
"image": imgUser,
"username": labUserName,
"message": labMessage,
"labTime": labTime,
]
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
}
}
TableViewController.swift:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Auto-set the UITableViewCells height (requires iOS8+)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
cell.labUserName.text = "Name"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .ShortStyle)
return cell
}
}
You will expect a display like this (iPhone landscape):
This is the update for swift 3 of the answer Imanou Petit.
CustomTableViewCell.swift:
import Foundation
import UIKit
class CustomTableViewCell: UITableViewCell {
let imgUser = UIImageView()
let labUerName = UILabel()
let labMessage = UILabel()
let labTime = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
imgUser.backgroundColor = UIColor.blue
imgUser.translatesAutoresizingMaskIntoConstraints = false
labUerName.translatesAutoresizingMaskIntoConstraints = false
labMessage.translatesAutoresizingMaskIntoConstraints = false
labTime.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgUser)
contentView.addSubview(labUerName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
let viewsDict = [
"image" : imgUser,
"username" : labUerName,
"message" : labMessage,
"labTime" : labTime,
] as [String : Any]
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Settigns.swift:
import Foundation
import UIKit
class Settings: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var myTableView: UITableView!
private let sections: NSArray = ["fruit", "vegitable"] //Profile network audio Codecs
private let fruit: NSArray = ["apple", "orange", "banana", "strawberry", "lemon"]
private let vegitable: NSArray = ["carrots", "avocado", "potato", "onion"]
override func viewDidLoad() {
super.viewDidLoad()
// get width and height of View
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
myTableView = UITableView(frame: CGRect(x: 0, y: barHeight+navigationBarHeight, width: displayWidth, height: displayHeight - (barHeight+navigationBarHeight)))
myTableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell") // register cell name
myTableView.dataSource = self
myTableView.delegate = self
//Auto-set the UITableViewCells height (requires iOS8+)
myTableView.rowHeight = UITableViewAutomaticDimension
myTableView.estimatedRowHeight = 44
self.view.addSubview(myTableView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// return the number of sections
func numberOfSections(in tableView: UITableView) -> Int{
return sections.count
}
// return the title of sections
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section] as? String
}
// called when the cell is selected.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
if indexPath.section == 0 {
print("Value: \(fruit[indexPath.row])")
} else if indexPath.section == 1 {
print("Value: \(vegitable[indexPath.row])")
}
}
// return the number of cells each section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return fruit.count
} else if section == 1 {
return vegitable.count
} else {
return 0
}
}
// return cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
if indexPath.section == 0 {
cell.labUerName.text = "\(fruit[indexPath.row])"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
} else if indexPath.section == 1 {
cell.labUerName.text = "\(vegitable[indexPath.row])"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
}
return cell
}
}
In Swift 5.
The custom UITableViewCell:
import UIKit
class CourseCell: UITableViewCell {
let courseName = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Set any attributes of your UI components here.
courseName.translatesAutoresizingMaskIntoConstraints = false
courseName.font = UIFont.systemFont(ofSize: 20)
// Add the UI components
contentView.addSubview(courseName)
NSLayoutConstraint.activate([
courseName.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
courseName.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
courseName.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
courseName.heightAnchor.constraint(equalToConstant: 50)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The UITableViewController:
import UIKit
class CourseTableViewController: UITableViewController {
private var data: [Int] = [1]
override func viewDidLoad() {
super.viewDidLoad()
// You must register the cell with a reuse identifier
tableView.register(CourseCell.self, forCellReuseIdentifier: "courseCell")
// Change the row height if you want
tableView.rowHeight = 150
// This will remove any empty cells that are below your data filled cells
tableView.tableFooterView = UIView()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath) as! CourseCell
cell.courseName.text = "Course name"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}

Resources