Custom Views with Storyboard

How can we create custom view over UITableViewController in Swift (StoryBoard)?

Try to drag a normal view and put on the top of your UITableView
Sample Image

It will seen like this

Sample Image

You can set an IBOutlet to it. Set its frame. Add buttons and add it to your view like any other views

self.view.addSubview(THIS_VIEW)

create a Custom View and use it in storyboard

Using constraints should set translatesAutoresizingmaskintoconstraint property to false. try to put this code before adding as subview.

imgView.translatesAutoresizingMaskIntoConstraints = false

Custom View don't show by adding UIView in storyboard to inherit from the customed class

What you need to do is prefix @IBInspectable before your vars.

Here is an example: custom class: Rainbow.swift

import Foundation
import UIKit

@IBDesignable
class Rainbow: UIView {
@IBInspectable var firstColor: UIColor = UIColor(red: (37.0/255.0), green: (252.0/255), blue: (244.0/255.0), alpha: 1.0)
@IBInspectable var secondColor: UIColor = UIColor(red: (171.0/255.0), green: (250.0/255), blue: (81.0/255.0), alpha: 1.0)
@IBInspectable var thirdColor: UIColor = UIColor(red: (238.0/255.0), green: (32.0/255), blue: (53.0/255.0), alpha: 1.0)

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}

override init(frame: CGRect) {
super.init(frame: frame)
}

func addOval(_ lineWidth: CGFloat, path: CGPath, strokeStart: CGFloat, strokeEnd: CGFloat, strokeColor: UIColor, fillColor: UIColor, shadowRadius: CGFloat, shadowOpacity: Float, shadowOffsset: CGSize) {

let arc = CAShapeLayer()
arc.lineWidth = lineWidth
arc.path = path
arc.strokeStart = strokeStart
arc.strokeEnd = strokeEnd
arc.strokeColor = strokeColor.cgColor
arc.fillColor = fillColor.cgColor
arc.shadowColor = UIColor.black.cgColor
arc.shadowRadius = shadowRadius
arc.shadowOpacity = shadowOpacity
arc.shadowOffset = shadowOffsset
layer.addSublayer(arc)
}

override func draw(_ rect: CGRect) {
// Add ARCs
self.addCirle(80, capRadius: 20, color: self.firstColor)
self.addCirle(150, capRadius: 20, color: self.secondColor)
self.addCirle(215, capRadius: 20, color: self.thirdColor)
}

func addCirle(_ arcRadius: CGFloat, capRadius: CGFloat, color: UIColor) {
let X = self.bounds.midX
let Y = self.bounds.midY

// Bottom Oval
let pathBottom = UIBezierPath(ovalIn: CGRect(x: (X - (arcRadius/2)), y: (Y - (arcRadius/2)), width: arcRadius, height: arcRadius)).cgPath
self.addOval(20.0, path: pathBottom, strokeStart: 0, strokeEnd: 0.5, strokeColor: color, fillColor: UIColor.clear, shadowRadius: 0, shadowOpacity: 0, shadowOffsset: CGSize.zero)

// Middle Cap
let pathMiddle = UIBezierPath(ovalIn: CGRect(x: (X - (capRadius/2)) - (arcRadius/2), y: (Y - (capRadius/2)), width: capRadius, height: capRadius)).cgPath
self.addOval(0.0, path: pathMiddle, strokeStart: 0, strokeEnd: 1.0, strokeColor: color, fillColor: color, shadowRadius: 5.0, shadowOpacity: 0.5, shadowOffsset: CGSize.zero)

// Top Oval
let pathTop = UIBezierPath(ovalIn: CGRect(x: (X - (arcRadius/2)), y: (Y - (arcRadius/2)), width: arcRadius, height: arcRadius)).cgPath
self.addOval(20.0, path: pathTop, strokeStart: 0.5, strokeEnd: 1.0, strokeColor: color, fillColor: UIColor.clear, shadowRadius: 0, shadowOpacity: 0, shadowOffsset: CGSize.zero)

}
}

It draws Rainbow

Source: https://www.appcoda.com/ibdesignable-ibinspectable-tutorial/

Try add this code :

override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}

func xibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask =
[.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}

func loadViewFromNib() -> UIView? {
guard let nibName = nibName else { return nil }
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(
withOwner: self,
options: nil).first as? UIView
}

Why can’t see the preview of my custom view in storyboard?

TL;DR; Call in prepareForInterfaceBuilder

// Call The Custom Setup Here
override func prepareForInterfaceBuilder() {
setupView()
}

Calling in layoutSubviews also works, but is called multiples times in runtime, prepareForInterfaceBuilder is called only for Designables Changes, and only with this purpose.

Long Code:

@IBDesignable
class CustomView: UIView {

override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}

override func prepareForInterfaceBuilder() {
setupView()
}

func InitChildPosition() {
var i = 1
for _view in self.subviews {
if _view is UIButton {
_view.center.x = (_view.bounds.width / 2)
_view.center.y = (_view.bounds.height / 2)
}

if _view is UIButton && i == 2 {
_view.center.x = self.bounds.width - (_view.bounds.width / 2)
_view.center.y = self.bounds.height - (_view.bounds.height / 2)
_view.backgroundColor = UIColor.darkGray
}
i += 1
}
}

func setupView() {
self.backgroundColor = UIColor.black

InitChildPosition()
}
}

Custom view controller class isn't listed in storyboard's class menu

The answer by @vikingosegundo, while explaining Xcode's complaint and being generally very informative, didn't help me solve my particular problem. My project was started in Xcode 8.3.3 and I already have lots of windows and views in the storyboard so I don't really want to abandon or work around the storyboard/generic issue.

That being said, I did some more research and came to the realization that many people prefer delegation to class inheritance so I decided to explore that approach. I was able to get something working that satisfies my needs.

I present here, a simplified, but functional approach.

First, a protocol that our data models must conform to:

protocol RestModel
{
static var entityName: String { get }
var id: Int { get }
}

Next, a data model:

///
/// A dummy model for testing. It has two properties: an ID and a name.
///
class ModelOne: RestModel
{
static var entityName: String = "ModelOne"
var id: Int
var name: String

init(_ id: Int, _ name: String)
{
self.id = id
self.name = name
}
}

Then, a protocol to which all classes that extend our base class must conform:

///
/// Protocol: ListViewControllerDelegate
///
/// All classes that extend BaseListViewController must conform to this
/// protocol. This allows us to separate all knowledge of the actual data
/// source, record formats, etc. into a view-specific controller.
///
protocol ListViewControllerDelegate: class
{
///
/// The actual table view object. This must be defined in the extending class
/// as @IBOutlet weak var tableView: NSTableView!. The base class saves a weak
/// reference to this variable in one of its local variables and uses that
/// variable to access the actual table view object.
///
weak var tableView: NSTableView! { get }

///
/// This method must perform whatever I/O is required to load the data for the
/// table view. Loading the data is assumed to be asyncronous so the method
/// must accept a closure which must be called after the data has been loaded.
///
func loadRecords()

///
/// This method must simply return the number of rows in the data set.
///
func numberOfRows() -> Int

///
/// This method must return the text that is to be displayed in the specified
/// cell.
/// - parameters:
/// - row: The row number (as supplied in the call to tableView(tableView:viewFor:row:).
/// - col: The column identifier (from tableColumn.identifier).
/// - returns: String
///
func textForCell(row: Int, col: String) -> String

} // ListViewControllerDelegate protocol

Now the actual base class:

class BaseListViewController: NSViewController,  
NSTableViewDataSource,
NSTableViewDelegate
{
//
// The instance of the extending class. Like most delegate variables in Cocoa
// applications, this variable must be set by the delegate (the extending
// class, in this case).
//
weak var delegate: ListViewControllerDelegate?

//
// The extending class' actual table view object.
//
weak var delegateTableView: NSTableView!

//
// Calls super.viewDidLoad()
// Gets a reference to the extending class' table view object.
// Sets the data source and delegate for the table view.
// Calls the delegate's loadRecords() method.
//
override func viewDidLoad()
{
super.viewDidLoad()
delegateTableView = delegate?.tableView
delegateTableView.dataSource = self
delegateTableView.delegate = self
delegate?.loadRecords()
delegateTableView.reloadData()
}


//
// This is called by the extending class' table view object to retreive the
// number of rows in the data set.
//
func numberOfRows(in tableView: NSTableView) -> Int
{
return (delegate?.numberOfRows())!
}


//
// This is called by the extending class' table view to retrieve a view cell
// for each column/row in the table. We call the delegate's textForCell(row:col:)
// method to retrieve the text and then create a view cell with that as its
// contents.
//
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
if let col = tableColumn?.identifier, let text = delegate?.textForCell(row: row, col: col)
{
if let cell = delegate?.tableView.make(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? NSTableCellView
{
cell.textField?.stringValue = text
return cell
}
}
return nil
}
} // BaseListViewController{}

And, finally, an extending class:

///
/// A concrete example class that extends BaseListViewController{}.
/// It loadRecords() method simply uses a hard-coded list.
/// This is the class that is specified in the IB.
///
class ViewOne: BaseListViewController, ListViewControllerDelegate
{
var records: [ModelOne] = []

//
// The actual table view in our view.
//
@IBOutlet weak var tableView: NSTableView!

override func viewDidLoad()
{
super.delegate = self
super.viewDidLoad()
}

func loadRecords()
{
records =
[
ModelOne(1, "AAA"),
ModelOne(2, "BBB"),
ModelOne(3, "CCC"),
ModelOne(4, "DDD"),
]
}

func numberOfRows() -> Int
{
return records.count
}

func textForCell(row: Int, col: String) -> String
{
switch col
{
case "id":
return "\(records[row].id)"

case "name":
return records[row].name

default:
return ""
}
}
} // ViewOne{}

This is, of course, a simplified prototype. In a real-world implementation, loading the records and updating the table would happen in closures after asynchronously loading the data from a database, web service, or some such.

My full prototype defines two models and two view controllers that extend BaseListViewClass. It works as desired. The production version of the base class will contain numerous other methods (which is why a wanted it to be a base class in the first place :-)

How to embed a custom view xib in a storyboard scene?

You're almost there. You need to override initWithCoder in your custom class you assigned the view to.

- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
}
return self; }

Once that's done the StoryBoard will know to load the xib inside that UIView.

Here's a more detailed explanation:

This is how your UIViewController looks like on your story board:
Sample Image

The blue space is basically a UIView that will "hold" your xib.

This is your xib:

Sample Image

There's an Action connected to a button on it that will print some text.

and this is the final result:

Sample Image

The difference between the first clickMe and the second is that the first was added to the UIViewController using the StoryBoard. The second was added using code.



Related Topics



Leave a reply



Submit