I've looked into many answers but either it's only UILabel or UIImage not both. So after trying to implement it I finally found that we cannot do two tableView.backgroundView. Here is what I've done so far:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
let image = UIImage(named: "noData")
let noDataImage = UIImageView(image: image)
noDataImage.frame = CGRect(x: 0, y: 10, width: 20, height: 20)
if allData.count == 0 {
noDataLabel.isHidden = false
noDataImage.isHidden = false
noDataLabel.text = "No data added. Add new entry \nby pressing the add icon on top right."
noDataLabel.textColor = UIColor.black
noDataLabel.numberOfLines = 3
noDataLabel.backgroundColor = UIColor.white
noDataLabel.textAlignment = .center
//what to do from here
tableView.backgroundView = noDataImage
tableView.backgroundView = noDataLabel
//end
tableView.separatorStyle = .none
return 0;
}
else {
noDataLabel.backgroundColor = UIColor.clear
noDataLabel.isHidden = true
noDataImage.isHidden = true
tableView.backgroundView = nil
return allData.count
}
I want to show an image and below that image I want to show a UILabel. How can I achieve this?
You need to create a view with subviews your image and label
var backgroundView =UIView(frame: CGRectMake(0, 0, your_width, your_height))
backgroundView.addSubview(noDataImage)
backgroundView.addSubview(noDataLabel)
tableView.backgroundView=backgroundView;
Note: Adjust the frame of noDataImage and noDataLabel as per your use.
backgroundView Property
The background view of the table view.
Declaration
Swift
var backgroundView: UIView?
Objective-C
#property(nonatomic, readwrite, retain) UIView *backgroundView
Discussion
A table view’s background view is automatically resized to match the size of the table view. This view is placed as a subview of the table view behind all cells, header views, and footer views.
You must set this property to nil to set the background color of the table view.
Related
I am trying to implement a custom Navigation Title on an iOS app.
The StoryBoard looks like this:
The place that I want to have the custom Navigation Title is the last view ( the message view ), and because I use an image and text this means that I need to have custom width and height. By needing this if I do in viewDidLoad:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationItem.titleView = titleView
The height of the title is blocked to 44pt.
But how I managed to do it is adding the subViews to the navigation bar:
var navigationBar: MessagesNavigationBar? {
guard let navigationBar = navigationController?.navigationBar as? MessagesNavigationBar else {
return nil
}
return navigationBar
}
And in viewDidLoad
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationBar?.addSubview(titleView!)
But the problem is that I have to remove the subviews when I leave the view, otherwise whatever I add there will be present in the table view as well.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if navigationBar != nil {
titleView?.removeFromSuperview()
}
}
Which kinda makes me feel that I'm not doing the right thing and I find difficult to add a fade out animation to those subViews when I leave the conversation. (i.e. native messages app on iOS).
So what is the right way of creating a custom Title Navigation Bar in iOS 12?
Scenes
Creating your custom titleView and assigning it to navigationItem.titleView is what you want. On older systems (pre iOS 11) you just might need to call sizeToFit() on the titleView.
This way you can create this titleView
Swift
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 20),
imageView.widthAnchor.constraint(equalToConstant: 20)
])
imageView.backgroundColor = .red
let titleLabel = UILabel()
titleLabel.text = "Custom title"
let hStack = UIStackView(arrangedSubviews: [imageView, titleLabel])
hStack.spacing = 5
hStack.alignment = .center
navigationItem.titleView = hStack
}
Obj-C
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] init];
[NSLayoutConstraint activateConstraints:#[
[imageView.heightAnchor constraintEqualToConstant:20],
[imageView.widthAnchor constraintEqualToConstant:20]
]];
imageView.backgroundColor = [UIColor redColor];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = #"Custom title";
UIStackView *hStack = [[UIStackView alloc] initWithArrangedSubviews:#[imageView, titleLabel]];
hStack.spacing = 5;
hStack.alignment = UIStackViewAlignmentCenter;
self.navigationItem.titleView = hStack;
}
You might also need to have the right set of autolayout constraints or use UIStackView.
These lines have no effect on the size of a title view:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
Instead (or in addition) give your title view a width constraint and a height constraint. That is how the runtime knows what size you want.
I have added a UIView using storyboard and subclassing it. Within this view, I am adding a UITableView programmatically. Here is the code to create the tableview and add it:
private func commonInit() {
self.backgroundColor = .clear
self.categoryTableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
categoryTableView?.delegate = self
categoryTableView?.dataSource = self
self.categoryTableView?.backgroundView = nil
self.categoryTableView?.backgroundColor = .yellow
self.categoryTableView?.isScrollEnabled = false
self.categoryTableView?.allowsMultipleSelection = true
self.addSubview(categoryTableView!)
}
This is what it looks like. I'm expecting the background of the tableview to be yellow (it is white in the screenshot)
I have also set the cells background color to clear which seems to be working. When I look at the UI hierarchy, it is pretty obvious that the White is coming from the the tableview.
I feel like this should be really strait forward. The strange part is the lines self.categoryTableView?.isScrollEnabled = false and self.categoryTableView?.allowsMultipleSelection = true both seem to be working, but the background color changing isn't.
I got the exactly same problem and found that just changing the tableView.backgroundColor after tableView.backgroundView = nil doesn't work.
My case also was a programmatically created UITableView inside an UIView.
The solution is to add a backgroundView to tableView and change the property backgroundColor of backgroundView object.
Swift 5 / iOS 12.x
Changing tableView background color
For any color other than .clear, the above should work:
self.tableView.backgroundView = UIView() //Create a backgroundView
self.tableView.backgroundView!.backgroundColor = .lightGray //choose your background color
Changing tableViewCells background color
Going a little further, some may find that the tableView background color didn't appear as intended because of the background colors of the UITableViewCell instances. A simple solution to make sure the cells have a transparent background:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.contentView.backgroundColor = UIColor.clear
cell.backgroundColor = .clear
}
I tested this in a playground, and the result was as expected.
import UIKit
import XCTest
import PlaygroundSupport
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
PlaygroundPage.current.liveView = view
view.backgroundColor = UIColor.blue
let tableView = UITableView(frame:CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height))
tableView.backgroundColor = UIColor.yellow
view.addSubview(tableView)
The result:
My guess is something else is afoot here. Maybe something with the sizing of the frames, or where you are calling the commonInit() method?
I am using Swift 3, Xcode 8.2.
I've been able to create a label to cover the empty table view cells when there are none to display.
My code is below and it is located in the subclass of UITableViewController.
override func numberOfSections(in tableView: UITableView) -> Int {
// if there are scans to display...
if items.count > 0 {
tableView.backgroundView = nil
tableView.separatorStyle = .singleLine
return 1
}
else { // otherwise, return 0, remove cell lines, and display a Label
let rect = CGRect(x: 0,
y: 0,
width: tableView.bounds.size.width,
height: tableView.bounds.size.height)
let noScanLabel: UILabel = UILabel(frame: rect)
noScanLabel.text = "No Scans"
noScanLabel.textColor = UIColor.gray
noScanLabel.font = UIFont.boldSystemFont(ofSize: 24)
noScanLabel.textAlignment = NSTextAlignment.center
tableView.backgroundView = noScanLabel
tableView.separatorStyle = .none
return 0
}
}
Here is the result.
Looks fine. But how, do I make it such that I include another line of text with a downward arrow pointing at the raised center button. Something like "Click here to start a scan"?
I've tried adding new line characters to the noScanLabel.text field but that didn't work out. Any pointers in the right direction would be helpful.
The simple solution is to set numberOfLines to 0 on noScanLabel. This way, the new lines will show.
let noScanLabel: UILabel = UILabel(frame: rect)
noScanLabel.text = "No Scans"
noScanLabel.textColor = UIColor.gray
noScanLabel.font = UIFont.boldSystemFont(ofSize: 24)
noScanLabel.numberOfLines = 0
noScanLabel.textAlignment = NSTextAlignment.center
Note than in such cases, I would recommend, for better maintainability, to actually remove the TableView from the UIViewController (hence not inherit from UITableViewController) and replace it with an empty view when you detect no scans are available. This will make each state more independent of each other and make maintenance easier.
There are a few ways achieve your goal. There is a well known library called DZNEmptyDataSet for handling empty tableviews and collectionviews . https://github.com/dzenbot/DZNEmptyDataSet
The other way would be to create a uiview with your specified rect and then add two labels to that uiview. One would be your noScanLabel and the other would be a label or image containing your arrow. You can set the layout constraints as required so that the arrow appears pointing down.
This code seems to work well. Change constraints if needed
let rect = CGRect(x: 0, y: 0, width: tableview.bounds.size.width, height: tableview.bounds.size.height)
let noDataView = UIView(frame: rect)
let noScanLabel = UILabel()
noScanLabel.text = "No Scans"
noScanLabel.textColor = UIColor.gray
noScanLabel.font = UIFont.boldSystemFont(ofSize: 24)
noScanLabel.textAlignment = NSTextAlignment.center
let arrowLabel = UILabel()
arrowLabel.text = "Add Arrow Image to this label"
arrowLabel.textColor = UIColor.gray
arrowLabel.font = UIFont.boldSystemFont(ofSize: 24)
arrowLabel.textAlignment = NSTextAlignment.center
noScanLabel.widthAnchor.constraint(equalToConstant: 100)
arrowLabel.widthAnchor.constraint(equalToConstant: 50)
noDataView.addSubview(noScanLabel)
noDataView.addSubview(arrowLabel)
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
noDataView.translatesAutoresizingMaskIntoConstraints = false
noScanLabel.translatesAutoresizingMaskIntoConstraints = false
self.tableview.addSubview(noDataView)
noDataView.isHidden = false
noDataView.centerXAnchor.constraint(equalTo: self.tableview.centerXAnchor).isActive = true
noDataView.centerYAnchor.constraint(equalTo: self.tableview.centerYAnchor).isActive = true
noScanLabel.centerXAnchor.constraint(equalTo: noDataView.centerXAnchor).isActive = true
noScanLabel.topAnchor.constraint(equalTo: noDataView.centerYAnchor).isActive = true
arrowLabel.centerXAnchor.constraint(equalTo: noDataView.centerXAnchor).isActive = true
arrowLabel.topAnchor.constraint(equalTo: noScanLabel.bottomAnchor).isActive = true
The other option is to set number of lines to zero as mentioned already
noScanLabel.numberLines = 0
You can take UIView and add your all UILabel and arrow Image on UIView and then assign that UIView to backgroundView of TableView.
Like this.
override func numberOfSections(in tableView: UITableView) -> Int {
// if there are scans to display...
if items.count > 0 {
tableView.backgroundView = nil
tableView.separatorStyle = .singleLine
return 1
}
else { // otherwise, return 0, remove cell lines, and display a Label
let rect = CGRect(x: 0,
y: 0,
width: tableView.bounds.size.width,
height: tableView.bounds.size.height)
let messageBaseView = UIView(frame: rect)
//Add your first label..
let noScanLabel: UILabel = UILabel()
noScanLabel.text = "No Scans"
noScanLabel.textColor = UIColor.gray
noScanLabel.font = UIFont.boldSystemFont(ofSize: 24)
noScanLabel.textAlignment = NSTextAlignment.center
messageBaseView.addSubView(noScanLabel)
//Add your second label.. and your arrow image here on messageBaseView
//Assign messageBaseView to backgroundView of tableView
tableView.backgroundView = messageBaseView
tableView.separatorStyle = .none
return 0
}
}
I have a header on my tableview, which has an image and a label. I want to center the UIImage and have the label pinned underneath it. This is my code currently:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerLabel = UILabel()
let logoView = UIImageView()
headerLabel.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 21)
headerLabel.text = self.navigationItem.title
headerLabel.textColor = UIColor.white
headerLabel.backgroundColor = UIColor.clear
headerLabel.textAlignment = NSTextAlignment.center
logoView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
let logo: UIImage = UIImage(named: self.navigationItem.title!)!
logoView.image = logo
logoView.contentMode = .scaleAspectFill
view.addSubview(headerLabel)
view.addSubview(logoView)
return topView
}
This puts the label centered on the top of the header, and the logo in the top left corner. How can I add constraints (programmatically, no storyboard) to center the image and pink the label below it? I've been using programmatic constraints quite a bit (i.e. something.leftAnchor.constraint(equalTo....) but I'm not sure how to apply it in this situation as it's my first use of a header.
I want to center the UIImage and have the label pinned underneath it.
This can be achieved by making the framing logic of each subview dependent of the neighbouring views. Whilst doing this programmatically, one has to be extra careful about the geometric calculations involved.
This snippet should do it:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let logoView = UIImageView()
logoView.frame = CGRect(x: tableView.center.x - 90/2, y: 0, width: 90, height: 90)
let logo: UIImage = UIImage(named: self.navigationItem.title!)!
logoView.image = logo
logoView.contentMode = .scaleAspectFill
view.addSubview(logoView)
let headerLabel = UILabel()
headerLabel.frame = CGRect(x: 0, y: logoView.frame.size.height, width: view.frame.width, height: 21)
headerLabel.text = self.navigationItem.title
headerLabel.textColor = UIColor.white
headerLabel.backgroundColor = UIColor.clear
headerLabel.textAlignment = NSTextAlignment.center
view.addSubview(headerLabel)
return topView
}
You can use that code to set default paddings.
if #available(iOS 15.0, *) { tableView.sectionHeaderTopPadding = 0 }
I need in my app a custom navigation bar with an image and a text but I can't add the text.
Here is the code to add the image, how can I add the title?
let logo = #imageLiteral(resourceName: "navigationbaricon")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
Thanks
Where is the frame assigned for self.navigationItem.titleView? Set the frame for imageView and it will work.
You can wrap the UIImageView and the UILabel (which will hold the custom title) in an UIView and then assign the UIView to the self.navigationItem.titleView. Something like this:
let view = UIView(...);
let label = UILabel(...);
label.text = "Custom Title";
let image = UIImageView(image: UIImage(named: "..."));
view.addSubview(image);
view.addSubview(label);
self.navigationItem.titleView = view;
This one is worked for me
override func viewDidLoad() {
super.viewDidLoad()
let titleView = UIView()
titleView.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
titleView.backgroundColor = UIColor.redColor()
let imageView = UIImageView(image: UIImage(named: "img")!)
imageView.frame = CGRectMake(0, 0, 40, 40)
imageView.contentMode = .ScaleAspectFill
titleView.addSubview(imageView)
self.navigationItem.titleView = titleView
}