Automatically set focus to first cell of UITableView when view loads - swift

I have been trying to make an application where when the view loads, the focus automatically goes to first cell of UITableview.
I have tried using the UIFocusGuide and then in prefferedFocusView() method return the tableView but that did not work.
I then wrote this code
var viewToFocus: UIView? = nil {
didSet {
if viewToFocus != nil {
print("called1 ")
self.setNeedsFocusUpdate();
self.updateFocusIfNeeded();
}
}
}
override weak var preferredFocusedView: UIView? {
if viewToFocus != nil {
print("called2 ")
return viewToFocus;
} else {
return super.preferredFocusedView;
}
}
In view did load
self.viewToFocus = myTableView
But that did not work as well.

I was able to get this by setting tableView.remembersLastFocusedIndexPath = true in viewDidLoad, then I implemented the delegate method for preferred focus:
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
return NSIndexPath(forItem: 0, inSection: 0)
}
When remembersLastFocusedIndexPath - If YES, when focusing on a table view the last focused index path is focused automatically. If the table view has never been focused, then the preferred focused index path is used.
Swift 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTableView.remembersLastFocusedIndexPath = true
}
and then in your delegate
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
return IndexPath(row: 0, section: 0)
}

Add the following code snippet to the implementation of the ViewController class with the tableview delegate invoked:
func tableView(tableView: UITableView, canFocusRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath.row == 0 {
return true
}
return false
}

In tableView:WillDisplayCell method add below code
[cell setSelected:YES animated:NO];
for first cell

You can set the scroll position automatically to go to the top cell.
let topCell:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.scrollToRowAtIndexPath(topCell, atScrollPosition: UITableViewScrollPosition.None, animated: true)
I am not sure if this is the same behaviour as preferredFocusedViewthough.

extension UITableView {
func scrollToTop(section: Int) {
let rows = self.numberOfRows(inSection: section)
if rows > 0 {
DispatchQueue.main.async {
let indexPath = IndexPath(row: rows - 1, section: section)
self.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
}
}
if there is only one section replace section with 0

Related

toggle button with boolean in UITableView Swift

I would like to add a boolean toggle to know whether the button has been pressed (will save this value to core data (also would like to save cell data to core data if true and delete from core data if false)) I am not sure how to do this. any help would be greatly appreciated. if all the code from the view controller is required leave a comment and I will do so (I have setup xcdatamodel already).
#IBAction func buttonTapped(_ sender:UIButton) {
var superView = sender.superview
while !(superView is UITableViewCell) {
superView = superView?.superview
}
let cell = superView as! UITableViewCell
if let indexpath = tableView.indexPath(for: cell){
print(indexpath)
}
}
Well I build a simple project and I figured something out using protocols,
First you define a protocol like this:
protocol cellDidRequestSaving {
func saveOrDelete(indexpath : Int)
}
First in your cell you define you button like this :
class TableViewCell: UITableViewCell {
var delegate: cellDidRequestSaving?
var indexPath = 0 //come from the parent
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func didTap(_ sender: Any) {
// this protocol defined in the parent
delegate?.saveOrDelete(indexpath: indexPath)
}
}
now in you tableViewController you use the protocol like this:
class TableViewController: UITableViewController, cellDidRequestSaving {
var cellStat = [Int:Bool]()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
// 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 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.indexPath = indexPath.row
cell.delegate = self
// Configure the cell...
return cell
}
func saveOrDelete(indexpath: Int) {
if let status = cellStat[indexpath], status {
print(indexpath, "delete")
cellStat[indexpath] = !status
}
else {
print(indexpath, "save")
cellStat[indexpath] = true
}
}
This is a simple project but you can get the hole idea about how to do it. Also, notice the protocol definition and usage so you wont miss anything.
and the out put is this
Add a property to your custom cell, for example:
var indexPath: IndexPath = IndexPath(row: -1, section: -1) //The -1 just know when it is not set yet
And in cellForRow in your tableView:
cell.indexpath = indexPath
Supposing that buttonTapped is defined in your custom cell class:
#IBAction func buttonTapped(_ sender:UIButton) {
print(indexpath)
}
Add a delegate method to your cell - (void)myCell:(MyCell *)cell didTapButton:(UIButton *)button
So then in your VC you can work backwards from the cell object.
- (void)myCell:(MyCell *)cell didTapButton:(UIButton *)button
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
MyObject *object = [self.dataSource objectAtIndexPath:indexPath];
// Do something with the knowledge that the button in the cell for the object was tapped...
// For example
object.tapped = YES;
[object.managedObjectContext save:NULL];
}
Just need to convert to swift ;)

Change UITableView section header when it's on top

I am using a custom header from Xib file for my table view using this code:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let result: Result = (self.responseData?.result![section])!
let headerCell = Bundle.main.loadNibNamed("HeaderViewTableViewCell", owner: self, options: nil)?.first as! HeaderViewTableViewCell
headerCell.sectionName.text = "Title"
if (section == 0) {
headerCell.sectionName.textColor = UIColor(red: 49/255.0, green: 149/255.0, blue: 213/255.0, alpha: 1)
}
return headerCell
}
Then I want to change the header sectionName when it is scrolled to top, I have tried this code
let topSection = self.mainTable .indexPathsForVisibleRows?.first?.section
let currentHeader : HeaderViewTableViewCell = self.mainTable .headerView(forSection: topSection!) as HeaderViewTableViewCell
currentHeader.sectionName.textColor = UIColor.red
But I get this error: Cannot convert value of type 'UITableViewHeaderFooterView?' to type 'HeaderViewTableViewCell' in coercion
Is there any way to cast the headerView to my custom type?
First of all I suggest you to use UITableViewHeaderFooterView for your header view. You can make a subclass and add custom code. For this example I will use an empty subclass:
class HeaderView: UITableViewHeaderFooterView {
override func prepareForReuse() {
super.prepareForReuse()
// set you default color (other properties) here.
// when scrolling fast the view gets reused and sometimes
// the view that's on top will suddenly appear on the bottom still with the previous values
textLabel?.textColor = .black
}
}
Register your header view (I am skipping all other unrelated code):
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(HeaderView.self, forHeaderFooterViewReuseIdentifier: "Header")
// If you have a nib file for your HeaderView then register nib instead
// Make sure in our nib file you set class name to HeaderView
// And the file name is also HeaderView.xib
// tableView.register(UINib.init(nibName: "HeaderView", bundle: nil) , forHeaderFooterViewReuseIdentifier: "Header")
}
Implement delegate methods:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header")
view?.textLabel?.text = "Hello"
return view
}
Create a method for updating headers:
// Iterates through section header views and
// checks for positions in relation to the tableview offset
func updateHeaders() {
var sectionHeaders: [Int: HeaderView?] = [:]
var i = 0
while i < numberOfSections {
sectionHeaders[i] = tableView.headerView(forSection: i) as? HeaderView
i += 1
}
let availableHeaders = sectionHeaders.flatMap { $0.value != nil ? $0 : nil }
for (index, header) in availableHeaders {
let rect = tableView.rectForHeader(inSection: index)
if rect.origin.y <= tableView.contentOffset.y + tableView.contentInset.top || index == 0 {
header!.textLabel?.textColor = .red
} else {
header!.textLabel?.textColor = .black
}
}
}
And call your updateHeaders() from UIScrollViewDelegate method scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateHeaders()
}
Also update headers before the view will be displayed (before any scroll appeared), for that use the UITableViewDelegate method willDisplayHeaderView:
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
updateHeaders()
}

How to make first table view cell as static and then load data from second cell using swift

class FiorstTableViewController: UITableViewController {
var data = ["1","2","3","4","5","6","7","8"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return 1
}
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCellfortable") as! CheckTableViewCell
cell.LabelNumber.text = data[indexPath.row]
if indexPath.row == 0 {
print("my first cell")
}
return cell
}
}
I need to load data from the second table view cell and making first cell as static.i have searched other stack overflow answers nothing works out for me..help me to do this one
You have two ways to implement that:
1) You can make two cell for UITableView first one always return when indexPath.row == 0
and for all return your SecondCellfortable
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50 //row count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCellfortable") as! CheckTableViewCellFirst
cell.LabelNumber.text = data[indexPath.row]
print("my first cell")
return cell
} else {
tableView.dequeueReusableCellWithIdentifier("SecondCellfortable") as! CheckTableViewCellSecond
return cell
}
}
OR
2) You can add the UITableViewHeader
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: tableView.sectionHeaderHeight))
view.backgroundColor = UIColor.redColor()
// Do your customization
return view
}
You cannot have static and dynamic cells in the same tableview. I recommend setting up your "first table cell" as a UIView then setting the tableheaderview to your custom view.
let headerView = Bundle.main.loadNibNamed("YourCustomView", owner: self, options: nil)?[0] as? YourCustomView
tableView.tableHeaderView = headerView

Swift: Unsatisfying constraints (auto layout) after tableView reloadSections

my problem looks like a weird behavior to me. I'm using Swift and iOS9+.
I've set up an UIViewController in storyboard with some views and a tableview. (Views are vertically above the tableview) All is set up properly with autolayout, no warnings and the UIViewController displays correctly.
In viewDidLoad() I request some table view data from an API and call tableView.reloadSections() after getting the response, the section fades correctly.
If I tap on a button in the section header, another view controller is presented where I can filter the requested data. After setting the filter, the view controller dismisses and the refreshVitalSigns(...) is called in the delegate.
Then again, I want to reload the table view section to only show the filtered data. When I call reloadSections() again, I get a lot of unsatisfying constraint warnings and the view is messed up, and I don't know why??????
With reloadData() everything works, but I only want to reload the section.
FYI: After requesting the API data, you have to scroll to see the whole table view content. If I scroll first, to see the whole content, and filter afterwards, also the reloadSections() works well! Obviously, it should also work without scrolling first...
Do you have any idea why this strange behavior happens?
I'm greatful for every hint!!!
Best
class JMProfileViewController: UIViewController {
/// Table view top spacing
#IBOutlet weak var tableViewTopSpacing: NSLayoutConstraint!
/// Table view
#IBOutlet var tableView: UITableView!
/// Attention view
#IBOutlet var attentionView: JMAttentionView?
var vitalSigns: [Items] = []
var data: [Items] = []
...
// View did load
override func viewDidLoad() {
super.viewDidLoad()
...
// Table view row height
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
// Register custom tableview header/footer views and cells
...
// Get table view data
let patientId = ...
getData(patientId)
}
/**
Get data
- parameter patientId: Patient ID
*/
func getData(patientId: Int) {
// Request
APIController.sharedInstance.getData(patientId: patientId) { response in
// Result handling
switch response {
case .Success(let result):
// Update vital signs
self.vitalSigns = result
self.data = result
// Reload data
self.tableView.beginUpdates()
self.tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
self.tableView.endUpdates()
case .Failure(let error):
print(error)
}
}
}
override func updateViewConstraints() {
super.updateViewConstraints()
// Set constraints depending on view's visibility
if let view = attentionView {
if view.hidden {
tableViewTopSpacing.constant = 0
} else {
tableViewTopSpacing.constant = view.bounds.height
}
}
}
// MARK: - Navigation
// Preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Data segue
if segue.identifier == SegueIdentifier.JMVitalSignsSegue.rawValue {
let vsvc = segue.destinationViewController as! JMVitalSignsViewController
vsvc.delegate = self
}
}
}
// MARK: - UITableViewDataSource
extension JMProfileViewController: UITableViewDataSource {
// Number of sections in table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
// Height for header in section
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 0 : UITableViewAutomaticDimension
}
// Estimated height for header in section
func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 0 : 27.0
}
// View for header in section
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
switch section {
case 0:
// First section without header
return nil
case 1:
// Configure header
let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier(CellIdentifier.JMTitleButtonHeaderView.rawValue) as! JMTitleButtonHeaderView
header.configure(NSLocalizedString("vitalSigns", comment: ""), buttonTarget: self, buttonImage: UIImage(named: "ic_filter_dark"), buttonAction: #selector(parameterButtonTapped(_:)))
return header
default:
// Configure header
let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier(CellIdentifier.JMTitleButtonHeaderView.rawValue) as! JMTitleButtonHeaderView
header.configure(NSLocalizedString("others", comment: ""))
return header
}
}
/**
Vital signs button tapped
*/
func parameterButtonTapped(sender: UIButton) {
// Show vital signs view controller
self.performSegueWithIdentifier(SegueIdentifier.JMVitalSignsSegue.rawValue, sender: self)
}
// Number of rows in section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows = 0
switch section {
case 0:
// Diagnosis
rows = 1
break
case 1:
// Vital signs
rows = data.count > 0 ? data.count : 1
break
case 2:
// Others
rows = 3
break
default:
break
}
return rows
}
// Cell for row at indexpath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier.JMSubtitleImageRightDetailCell.rawValue, forIndexPath: indexPath) as! JMSubtitleImageRightDetailCell
// Configure cell
...
return cell
case 1:
if data.count > 0 {
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier.JMTitleThreeLabelsSubtitleCell.rawValue, forIndexPath: indexPath) as! JMTitleThreeLabelsSubtitleCell
// Configure cell
let item = data[indexPath.row]
cell.configure(item.caption, unit: item.unit, values: item.values)
cell.accessoryType = .DisclosureIndicator
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier.JMBasicCell.rawValue, forIndexPath: indexPath)
// Configure cell
cell.textLabel?.text = NSLocalizedString("noData", comment: "")
cell.selectionStyle = .None
return cell
}
default:
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier.JMBasicCell.rawValue, forIndexPath: indexPath) as! JMDefaultCell
...
return cell
}
}
}
// MARK: - JMVitalSignsViewControllerDelegate
extension JMProfileViewController: JMVitalSignsViewControllerDelegate {
/**
Refresh vital signs
*/
func refreshVitalSigns(selectedItems: [Items]) {
print("Refresh vital signs")
var data: [Items] = []
for item in selectedItems {
for vitalItem in vitalSigns {
if item.match == vitalItem.match {
data.append(vitalItem)
break
}
}
}
self.data = data
// HERE IS MY PROBLEM
// tableView.beginUpdates()
// tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
// tableView.endUpdates()
tableView.reloadData()
}
}
Finally I've found a workaround.
I changed the UIViewController into a UITableViewController and added every custom UIView in the TableViewHeader (NOT Section header) with auto layout.
Now it works!

How do I programmatically select an object (and have that object show as selected) in the new NSCollectionView?

I have sucessfully implemented a 10.11 version of NSCollectionView in my Mac app. It displays the 10 items that I want, but I want the first item to be automatically selected when the app starts.
I have tried the following in the viewDidLoad and alternatively in the viewDidAppear functions;
let indexPath = NSIndexPath(forItem: 0, inSection: 0)
var set = Set<NSIndexPath>()
set.insert(indexPath)
collectionView.animator().selectItemsAtIndexPaths(set, scrollPosition: NSCollectionViewScrollPosition.Top)
I have tried line 4 above with and without the animator
I have also tried the following in place of line 4
collectionView.animator().selectionIndexPaths = set
with and without the animator()
While they both include the index path in the selected index paths, neither actually displays the item as selected.
Any clues where I am going wrong?
I propose not to use the scroll position. In Swift 3 the following code in viewDidLoad is working for me
// select first item of collection view
collectionView(collectionView, didSelectItemsAt: [IndexPath(item: 0, section: 0)])
collectionView.selectionIndexPaths.insert(IndexPath(item: 0, section: 0))
The second code line is necessary, otherwise the item is never deselected.
The following is working, too
collectionView.selectItems(at: [IndexPath(item: 0, section: 0)], scrollPosition: NSCollectionViewScrollPosition.top)
For both code snippets it is essential to have a NSCollectionViewDelegate with the function
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
// if you are using more than one selected item, code has to be changed
guard let indexPath = indexPaths.first
else { return }
guard let item = collectionView.item(at: indexPath) as? CollectionViewItem
else { return }
item.setHighlight(true)
}
According to Apple's docs, programmingly selecting items using NSCollectionView's methods won't call NSCollectionViewDelegate's didSelect method. So you have to add the highlight yourself.
override func viewDidAppear() {
super.viewDidAppear()
retainSelection()
}
private func retainSelection() {
let indexPath = IndexPath(item: 0, section: 0)
collectionView.selectItems(at: [indexPath], scrollPosition: .nearestVerticalEdge)
highlightItems(true, atIndexPaths: [indexPath])
}
private func highlightItems(_ selected: Bool, atIndexPaths: Set<IndexPath>) {
for indexPath in atIndexPaths {
guard let item = collectionView.item(at: indexPath) else {continue}
item.view.layer?.backgroundColor = (selected ? NSColor(named: "ItemSelectedColor")?.cgColor : NSColor(named: "ItemColor")?.cgColor)
}
}
I think you use view.layer for display of selection state. And it looks like NSCollectionViewItem hasn't layer at moment. If you subclass NSCollectionViewItem try to enable wantsLayer property of it's view in viewDidLoad method.
override func viewDidAppear() {
super.viewDidLoad()
self.view.wantsLayer = true
}
Also you can enable wantsLayer in func collectionView(NSCollectionView, itemForRepresentedObjectAt: IndexPath) -> NSCollectionViewItem.
func collectionView(collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath
indexPath: NSIndexPath) -> NSCollectionViewItem {
let item = self.collectionView.makeItemWithIdentifier("dataSourceItem", forIndexPath: indexPath)
// Configure the item ...
item.view.wantsLayer = true
return item
}
Then you can call
collectionView.selectionIndexPaths = set
and be sure it works.