For adding a view to a UITableViewController I added the view to navigationController as below:
self.navigationController?.view.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.rightAnchor.constraint(equalTo: (self.navigationController?.view.rightAnchor)!).isActive = true
view.bottomAnchor.constraint(equalTo: (self.navigationController?.view.bottomAnchor)!).isActive = true
view.widthAnchor.constraint(equalToConstant: 70).isActive = true
view.heightAnchor.constraint(equalToConstant: 120).isActive = true
But when you want to push a new ViewController it will keeps the added view (myView).
I tried to add myView to view and tableView like below:
self.view.addSubview(myView)
self.tableView.addSubview(myView)
but both doesn't work.
I know I can use UIViewController and add a UITableView and then it is easier to add myView to UIViewController.
Should I add myView to another view?
The View of a UITableViewController is a UITableView, so you cannot add subviews to the controller on top of the table.
You have to derive from UIViewController to get full layout control.
Instead of using UITableViewController, use a UIViewController and put a UITableView within it.
Yes it's possible in UITableViewConroller:
let bottomView = UIView()
bottomView.backgroundColor = .red // or your color
bottomView.frame = CGRect(x: 0, y: UIScreen.main.bounds.size.height - 78, width: tableView.frame.size.width, height: 78) // 78 or your size of view
navigationController?.view.addSubview(bottomView)
tableView.tableFooterView = UIView()
bottomView.backgroundColor = .red // or your color
bottomView.frame = CGRect(x: 0, y: UIScreen.main.bounds.size.height - 78, width: tableView.frame.size.width, height: 78) // 78 or your size of view
navigationController?.view.addSubview(bottomView)
tableView.tableFooterView = UIView()[enter image description here][1]
Related
I am working on a project that uses a UITabBarController for displaying all the different UIViewControllers but now I need to add a mini player just in between the tabBar and the navigation view (ViewControllers will have to resize too).
Is there anyway I can achieve that by reusing the existing class?
EDIT
I have tried 2 methods:
1- Adding it into the view. Gets Added but above of the VCs
let aView = UIView()
view.addSubview(aView)
aView.backgroundColor = .white
aView.anchor(top: nil, leading: view.leadingAnchor, bottom: tabBar.topAnchor, trailing: view.trailingAnchor, size: .init(width: 0, height: 100))
2- Adding it into the tabBar. It might sound silly but I thought It would work.
let viewOverTabBar = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
viewOverTabBar.backgroundColor = UIColor.black
tabBar.addSubview(viewOverTabBar)
Add your view as subview to view of UITabBarViewController not tab bar itself. Just place it above tab bar.
Also change:
aView.anchor(top: nil, leading: view.leadingAnchor, bottom: tabBar.topAnchor, trailing: view.trailingAnchor, size: .init(width: 0, height: 100))
to setting directly frame property of your view.
Also you need to do in in viewWillAppear method.
You can try this way :
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.createSmallPlayer()
}
func createSmallPlayer() {
let viewOverTabBar = UIView(frame: CGRect(x: 0, y: self.tabBar.frame.origin.y-40, width: self.tabBar.frame.size.width, height: 30))
viewOverTabBar.backgroundColor = UIColor.brown
//viewOverTabBar.layer.cornerRadius = viewOverTabBar.frame.size.height/2
viewOverTabBar.layer.masksToBounds = false
viewOverTabBar.layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
viewOverTabBar.layer.shadowRadius = 5.0
viewOverTabBar.layer.shadowOffset = CGSize(width: 0.0, height: -5.0)
viewOverTabBar.layer.shadowOpacity = 0.5
//tabBar.addSubview(viewOverTabBar)
view.addSubview(viewOverTabBar)
}
}
And make sure that your all other view controller(which will navigate within tabbar) adjust frame accordingly.
Either you have to manage bottom view of all view controller by 30 pixels up and keep 30 pixels space blank at bottom, so no any content hide behind your player view.
Or you have you add :
Container view UIView same as added player view.
In that you have to add view controller's view with navigation controller as subview(Refer this : Adding a view controller as a subview in another view controller).
I forgot to add an x-component to my autolayout, but I was still able to see the view. I was wondering how/if autolayout generates default constraints when used programatically because in IB, there would be errors. No errors are printed in the debug console for this either.
I notice that when I do not specify an x-component, the view will always be left anchored to its parent view. Is there documentation which states what the default values are when a constraint is missing?
import UIKit
import PlaygroundSupport
//
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let outBox = UIView()
outBox.backgroundColor = UIColor.blue
view.addSubview(outBox)
outBox.translatesAutoresizingMaskIntoConstraints = false
outBox.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
outBox.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
outBox.widthAnchor.constraint(equalToConstant: 200).isActive = true
outBox.heightAnchor.constraint(equalToConstant: 200).isActive = true
let inBox = UIView(frame: CGRect(x: 100, y: 2000, width: 10, height: 10))
inBox.backgroundColor = UIColor.red
outBox.addSubview(inBox)
inBox.translatesAutoresizingMaskIntoConstraints = false
inBox.topAnchor.constraint(equalTo: outBox.topAnchor).isActive = true
inBox.bottomAnchor.constraint(equalTo: outBox.bottomAnchor).isActive = true
inBox.widthAnchor.constraint(equalToConstant: 25).isActive = true
// NO x-constraint component.. Should raise missing constraints error.
}
}
PlaygroundPage.current.liveView = MyViewController()
The key isn't in setting frame here
let inBox = UIView(frame: CGRect(x: 100, y: 2000, width: 10, height: 10))
but it's here
inBox.translatesAutoresizingMaskIntoConstraints = false
that line ignores the internal conversion of frame to constraints and defaults them to zero based , insuffieicent constraints don't mean you can't see the view , for example you can do the same in IB and still see the view with red border and after run also but it doesn't mean it's properly set , and this as finally constraints will be converted to frame so it's a coincidence regarding zero-based
I am attempting to include a segmentedControl on my navBar that looks like this:
The idea here is that the text "fetching..." is a small titleView. However, my current implementation would result in the text "fetching..." on the lower side like so:
I implement large titles so that I can get two "rows" on the navBar, else the word "fetching..." will be hidden behind the segmentedControl.
Code:
let segmentedControl: UISegmentedControl = {
let items = ["Now","In 15 mins", "In 1 hour"]
let sc = UISegmentedControl(items: items)
sc.selectedSegmentIndex = 0
return sc
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.backBarButtonItem?.title = "Back"
navigationItem.largeTitleDisplayMode = .automatic
navigationItem.titleView = segmentedControl
}
Does anyone have any advice?
You can create a customView that holds all the views you want to show in the navigation bar and set that view as titleView like below,
let segmentedControl: UISegmentedControl = {
let items = ["Now","In 15 mins", "In 1 hour"]
let sc = UISegmentedControl(items: items)
sc.selectedSegmentIndex = 0
return sc
}()
let fetchingLabel: UILabel = {
let label = UILabel(frame: .zero)
label.text = "Fetching..."
return label
}()
In viewDidLoad
let customView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 250))
customView.addSubview(segmentedControl)
customView.addSubview(fetchingLabel)
fetchingLabel.frame = CGRect(x: 150, y: 0, width: self.view.frame.width, height: 60)
segmentedControl.frame = CGRect(x: 60, y: 50, width: self.view.frame.width * 0.75, height: 30)
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .automatic
navigationItem.titleView = customView
This should give you below result. You can play with the values to do what you want.
after adding this codes to appDelegate inside didFinishLaunchingWithOptions function:
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainView = PageViewController(coder: NSCoder())
let nav1 = UINavigationController(rootViewController: mainView!)
nav1.navigationBar.isTranslucent = false
self.window!.rootViewController = nav1
self.window?.makeKeyAndVisible()
tableView height is not looking good, some part of tableView(cells) not fitting screen
my codes for implementing tableView:
let myTableView = UITableView(frame: CGRect(x: 0, y: 40, width: screenSize.width, height: screenSize.height-40))
myTableView.bounces = false
myTableView.tableFooterView = UIView()
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID")
myTableView.register(MyTableViewCell.self, forCellReuseIdentifier: "cellID2")
myTableView.dataSource = self
myTableView.delegate = self
for show all part of tableView in screen i have to change the first line above code to this:
let myTableView = UITableView(frame: CGRect(x: 0, y: 40, width: screenSize.width, height: screenSize.height-90))
why this is happening and what should i do to solve this problem thanks
Try something like this but don't forget to add myTableView as a subview before you set anchors:
myTableView.translatesAutoresizingMaskIntoConstraints = false
myTableView.leftAnchor.constraint(equalTo: myTableView.superview!.leftAnchor).isActive = true
myTableView.rightAnchor.constraint(equalTo: myTableView.superview!.rightAnchor).isActive = true
myTableView.topAnchor.constraint(equalTo: myTableView.superview!.topAnchor).isActive = true
myTableView.bottomAnchor.constraint(equalTo: myTableView.superview!.bottomAnchor).isActive = true
I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView