I created a custom MarkerView from a .xib but it's always not centered in the proper set.
final class ChartMarkerView: MarkerView, ViewLoadable {
#IBOutlet weak var laValue: UILabel!
override required init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func setUI() {
}
public override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
laValue.text = String(entry.y)
layoutIfNeeded()
}
}
protocol ViewLoadable {
var nibName: String { get }
}
extension ViewLoadable where Self: UIView {
var containerView: UIView? { return subviews.first }
var nibName: String { return String(describing: type(of: self)) }
func loadViewFromXib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
func xibSetup() {
let view = loadViewFromXib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleTopMargin]
view.autoresizesSubviews = true
backgroundColor = .clear
addSubview(view)
}
}
Turns out I just need to calculate the offset of the MarkerView and set it when loading the view:
offset.x = -self.frame.size.width / 2.0
offset.y = -self.frame.size.height - 7.0
Related
cannot modify value for a variable in my XIB class. How can I set variable from the didLoad of ViewController the value of var orientation ? I only get printed always the initial default value I set in the class .none
in my VC
override func viewDidLoad() {
super.viewDidLoad()
self.myTestCustomView.orientation = .square
}
my class
enum Orientation: String, CaseIterable {
case none
case horizontalRectangle, verticalRectangle, square
}
class CustomView : UIView {
#IBOutlet private var contentView:UIView?
// other outlets
private var _orientation: Orientation = .none
var orientation: Orientation {
set { _orientation = newValue }
get {return _orientation}
}
override init(frame: CGRect) { // for using CustomView in code
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) { // for using CustomView in IB
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
guard let content = contentView else { return }
content.frame = self.bounds
content.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.addSubview(content)
print("checkValue\(_orientation.rawValue)")
self.setNeedsLayout()
}
}
EDIT:
my solution, removed _orientation, accessing from VC directly to orientation. any advice or comment welcomed.
var orientation: Orientation {
set {
switch newValue {
case .horizontalRectangle:
self.contentView.layer.cornerRadius = self.frame.height / 5.0 //height since is orizzontal rectangle
case .verticalRectangle:
self.contentView.layer.cornerRadius = self.frame.width / 5.0
case.square:
self.contentView.layer.cornerRadius = self.frame.width / 5.0
case .none:
self.contentView.layer.cornerRadius = 0.0
}
self.setNeedsLayout()
}
get {return self.orientation}
}
I am looking for a way to refer to the IBOutlet variable in a custom class UIView, from another class. I found layoutSubviews, but each change only works on the first call, and not on each subsequent call. Thanks for help!
ViewController class:
var SB = StatusBar()
SB.update(1)
SB.update(2)
SB.update(3)
StatusBar class:
class StatusBar: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var label: UILabel!
var ActualStatus: Int!
required init?(coder: NSCoder) {
super.init(coder: coder)
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
func update(status) {
ActualStatus = status
self.layoutSubviews()
}
override func layoutSubviews() {
super.layoutSubviews()
label.text = ActualStatus
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of:self))
let nib = UINib(nibName: "StatusBar", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
Result: label.text is 1, change only works on the first call
Why in the code below, text changes as value, but there is no change in UIVIew. How do I refresh the data in a custom class? The value changes but does not refresh in the view. I have no ideas anymore.
ViewController.swift:
let SB = StatusBar()
SB.update()
StatusBar.swift:
class StatusBar: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var Status: UITextField!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
func update() {
Status.text = "ok"
print(Status.text)
print("status updated.")
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of:self))
let nib = UINib(nibName: "StatusBar", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
You are not calling update(). In xibSetup(), add update()
I try to add a subview to a view, but after added, the button on subview is untouchable. I have check the status of button, user interaction is enable. and after add subview, the UI of subview are show up correctly.
The subview class:
class ErrorInfoView: UIView {
#IBOutlet var containerView: UIView!
#IBOutlet weak var messageLabel: UILabel!
var callback: (()->())?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed("ErrorInfoView", owner: self, options: nil)
containerView.frame = self.bounds
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.backgroundColor = UIColor.clear
addSubview(containerView)
}
func configure(message: String, callback: #escaping ()->()) {
self.messageLabel.text = message
self.callback = callback
}
#IBAction func pressRetryButton(_ sender: UIButton) {
self.callback?()
}
}
The parent view:
let errorView = ErrorInfoView(frame: self.tableViewContainer.frame)
errorView.configure(message: error) {
// do something, but not be called
}
self.tableViewContainer.addSubview(errorView)
self.tableViewContainer.bringSubviewToFront(errorView)
errorView.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = errorView.centerXAnchor.constraint(equalTo: self.tableViewContainer.centerXAnchor)
let verticalConstraint = errorView.centerYAnchor.constraint(equalTo: self.tableViewContainer.centerYAnchor)
self.tableViewContainer.addConstraints([horizontalConstraint, verticalConstraint])
Every where I look for how to make a custom UI View with a nib I see the following code to use
class CustomView: UIView {
var contentView: UIView!;
#IBOutlet weak var sampleLabel: UILabel!
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
if self.subviews.count == 0 {
nibSetup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if self.subviews.count == 0 {
nibSetup()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
nibSetup()
}
}
fileprivate func nibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
}
fileprivate func loadViewFromNib() -> TimerView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
return nibView
}
}
But this basically loads a new instance of the custom view (Loaded from the nib) into a contentView on the original instance. This works until you want to call methods on your view from the view controller it is in. When you want to call a method on the instance from the view controller you are calling it on the original instance not the instance that is in cotnentView so the result is nothing happens.
As a work around I have declared the contentView to be a CustomView instead of a UIView and then my public methods call methods on the content view
ie
class CustomView: UIView {
var contentView: CustomView!;
#IBOutlet weak var sampleLabel: UILabel!
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
if self.subviews.count == 0 {
nibSetup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
if self.subviews.count == 0 {
nibSetup()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
nibSetup()
}
}
fileprivate func nibSetup() {
contentView = loadViewFromNib()
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
}
fileprivate func loadViewFromNib() -> TimerView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! CustomView
return nibView
}
func setLabelText(blah: String) {
sampleLabel.text = blah
}
public func setLabelTextFromParent(words: String) {
contentView.setLabelText(blah: words)
}
}
This seems really hacky though. There has to be a much better way of doing this!
Can someone explain to me how to do this so that I only have one instance of my custom view rather than one nested in another when instantiating it from IB. Thanks.
You can define an extension on UIView like
extension UIView {
class func fromNib<T : UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
I don't see any hack here.
Call it like
let myCustomView: MyCustomView = UIView.fromNib()