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}
}
Related
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
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()
I've subclassed an NSTextField with the following code:
import Cocoa
class CustomSearchField: NSTextField {
override func draw(_ dirtyRect: NSRect) {
self.wantsLayer = true
let textFieldLayer = CALayer()
self.layer = textFieldLayer
self.backgroundColor = NSColor.white
self.layer?.backgroundColor = CGColor.white
self.layer?.borderColor = CGColor.white
self.layer?.borderWidth = 0
super.cell?.draw(withFrame: dirtyRect, in: self)
}
}
class CustomSearchFieldCell: NSTextFieldCell {
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let minimumHeight = self.cellSize(forBounds: rect).height
let newRect = NSRect(x: rect.origin.x + 25, y: (rect.origin.y + (rect.height - minimumHeight) / 2) - 4, width: rect.size.width - 50, height: minimumHeight)
return super.drawingRect(forBounds: newRect)
}
}
This is all working fine and it draws my NSTextField just as I wanted. The only problem is, as soon as I make some other part of the interface the first responder (clicking outside the NSTextField), the text inside the NSTextField (placeholder or filled in text) is fading out. As soon as I click on it again, it fades back in. I've been searching for quiet a while now, but can't really figure out why this is happening. I just want the text to be visible all the time, instead of fading in and out.
It has to do with the CALayer that I'm adding to accomplish my styling.
Whenever I run the same settings from viewDidLoad on the textfield, it works like a charm. For example:
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSTextField!
override func viewDidLoad() {
initCustomSearchField()
}
private func initCustomSearchField() {
searchField.wantsLayer = true
let textFieldLayer = CALayer()
searchField.layer = textFieldLayer
searchField.backgroundColor = NSColor.white
searchField.layer?.backgroundColor = CGColor.white
searchField.layer?.borderColor = CGColor.white
searchField.layer?.borderWidth = 0
searchField.delegate = self
}
}
draw method should really be used to draw the view not setting the properties.And for your problem don't set to self.layer directly use sublayer instead .
My suggestion for your code:
class CustomTextField :NSTextField {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
textColor = .green
}
}
class CustomTextFieldCell: NSTextFieldCell {
override init(textCell string: String) {
super.init(textCell: string)
setupView()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
backgroundColor = .red
}
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let newRect = NSRect(x: (rect.width - rect.width/2)/2, y: 0, width: rect.width/2, height: 20)
return super.drawingRect(forBounds:newRect)
}
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
super.draw(withFrame: cellFrame, in: controlView)
controlView.layer?.borderColor = NSColor.white.cgColor
controlView.layer?.borderWidth = 2
}
}
I want to put all subview properties and my subview setup code in a UIView subclass and load that into my UIViewController subclass using loadView(). Then access the UIView subclass members without casting the view property of UIViewController all the time.
This is my UIView subclass AwesomeClass
class AwesomeView: UIView {
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
And my UIViewController subclass AwesomeViewController
class AwesomeViewController: UIViewController {
override func loadView() {
let view = AwesomeView()
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I could do something like:
var subclassedView:AwesomeView {
get {
return self.view as! AwesomeView
}
}
and
subclassedView.testView.backgroundColor = UIColor.blue
But is there a way to call testView directly with self.view in the AwesomeViewController?
Edit:
What I am looking for is Covariant return type in swift.
You can you something like this:
Instantiate an instance of AwesomeViewController in AwesomeView
class AwesomeView: UIView {
var exampleColorVariable:UIColor?
//here you instantiate your view controller
var awesomeViewController = AwesomeViewController()
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
then you can access any method in AwesomeView changing a little bit your code
class AwesomeViewController: UIViewController {
lazy var awesomeView: AwesomeView = {
let view = AwesomeView()
view.awesomeViewController = self
return view
}()
func setupView() {
view.addSubview(awesomeView)
// your constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
// NOW YOU CAN ACCESS ANY METHOD IN YOUR VIEW awesomeView.yourFunction()
// or you access that variable
awesomeView.exampleColorVariable = .red // you can now omit UIColor in swift3
}
}
I'm struggling with problem in rotating image in a UIScrollView, my view hierarchy is described by:
I've an UIScrollView with an UIView as subview. The UIView has an UIImageView as subview.
I would be able to rotate the imageView inside it and then recalculate the contentSize of the scrollView in order to be able to pan over all of it.
I'm using this code:
//
// TestView.swift
//
import UIKit
class TestView: UIView {
// MARK: - properties
private var imageView: UIImageView?
private var zoomingView: UIView?
private var scrollView: UIScrollView?
private let kDecayRotation = "kDecayRotation"
var image: UIImage? {
didSet {
setupImageView(image)
}
}
// MARK: - init
convenience init(frame: CGRect, image: UIImage) {
self.init(frame: frame)
setupImageView(image)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blueColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - methods
private func setupImageView(image: UIImage?) {
guard let image = image else { return }
imageView = UIImageView(image: image)
zoomingView = UIView(frame: imageView!.frame)
zoomingView?.addSubview(imageView!)
scrollView = UIScrollView(frame: frame)
scrollView?.backgroundColor = UIColor.redColor()
scrollView?.contentSize = zoomingView!.frame.size
scrollView?.addSubview(zoomingView!)
addSubview(scrollView!)
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:)))
rotationGesture.delegate = self
scrollView?.addGestureRecognizer(rotationGesture)
}
#objc private func handleRotation(gestureRecognizer: UIRotationGestureRecognizer) {
adjustAnchorPointForGestureRecognizer(gestureRecognizer)
guard let targetView = zoomingView else { return }
if gestureRecognizer.state == .Began { }
else if gestureRecognizer.state == .Changed {
targetView.transform = CGAffineTransformRotate(targetView.transform, gestureRecognizer.rotation)
gestureRecognizer.rotation = 0
scrollView?.contentSize = targetView.frame.size
var targetFrame = targetView.frame
let targetBounds = targetView.bounds
targetFrame.origin.x = 0
targetFrame.origin.y = 0
targetView.frame = targetFrame
targetView.bounds = targetBounds
scrollView?.setNeedsDisplay()
} else if gestureRecognizer.state == .Cancelled || gestureRecognizer.state == .Ended { }
}
// MARK: - gesture helper
private func adjustAnchorPointForGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
guard let zoomingView = zoomingView else { return }
if gestureRecognizer.state == .Began {
let locationInView = gestureRecognizer.locationInView(zoomingView)
let locationInSuperview = gestureRecognizer.locationInView(zoomingView.superview)
zoomingView.layer.anchorPoint = CGPointMake(locationInView.x / zoomingView.bounds.size.width, locationInView.y / zoomingView.bounds.size.height)
zoomingView.center = locationInSuperview
}
}
}
extension TestView: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
The problem are:
The View doesn't rotate around the location of the finger (although I implemented the method to calculate the new anchorPoint)
The contentSize if recalculated correctly (only if I comment adjustAnchorPointForGestureRecognizer(gestureRecognizer) in handleRotation(_:)), but the image move across the scrollview in an horrible way. How can I get the effect of the image remain fixed (and rotate) and the contentSize being recalculated under of that in a 'silence' manner?
Edit 1
Here's the test image:
The code to use the TestView is:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
let sampleImage = UIImage(named: "testImage")
let testView = TestView(frame: view.frame, image: sampleImage!)
view.addSubview(testView)
}
}