I'm writing nib-less views in which I use autolayout for all my layout logic. I find myself having to turn off autoresizing with every view I instantiate. My code is littered with a lot of these:
view.translatesAutoresizingMaskIntoConstraints
Ideally I'd like to just
extension UIView/NSView {
override var translatesAutoresizingMaskIntoConstraints: Bool = false
}
and get it over with once and for all, but extensions can't override stored properties.
Is there some other simple way to switch off autoresizing for good?
Well just a suggestion since its annoying to always set that to false, just setup a function with all the shared setups for the UIView and call it every time,
its saves time and its kinda less annoying than trying and setting the values each time,
extension UIView {
func notTranslated() {
self.translatesAutoresizingMaskIntoConstraints = false
//Add any additional code.
}
}
//Usage
let view = UIView()
view.notTranslated()
You can't override this constraints properties because the UIView maybe declared in the IB
translatesAutoresizingMaskIntoConstraints according to apple.
By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false.
imagine if you could override that from an extension that would lead to some conflicts if there was other UIView's that's have the opposite value True || false, so in my opinion:
Apple did this to prevent any conflicts with the views constrains, therefore if you don't like to write it every time just wrap it up in a function.
Please if anyone have additional information, don't hesitate to contribute.
UPDATE: I found this cool answer that could also work, check out the code below.
class MyNibless: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
var nibless: UIView = MyNibless()
//Usage
nibless.updateConstraints()
print(nibless.translatesAutoresizingMaskIntoConstraints) //false
So simply just create MyNibless instance as UIView and this also open big door to customizations too
Related
I'm pretty new to coding. Im not sure if an IBOutlet (button, text field, etc) ctrl-dragged from a xib should go in the xib's NSView class or in the view controller which has the NSView added as a subview.
I've been playing around with this for a while, learning as I go. I'm stuck on wondering if I have the code structured correctly. This is for MacOS so resources are limited and often dated. I'd assume that an outlet added for a button, for example, would go in the controller as views should be "dumb". If I try that the actions always have "action" set automatically and type as Any as a default - not what I'm used to seeing. I suspect this may have something to do with the class set for the file's owner and the class set for the view in IB. If anyone can outline the best way to handle this that would be fantastic, thank you!
The view that loads the xib:
class View4: NSView {
#IBOutlet weak var view: View4!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
Bundle.main.loadNibNamed("View4", owner: self, topLevelObjects: nil)
self.frame = self.bounds
self.wantsLayer = true
self.translatesAutoresizingMaskIntoConstraints = false
self.layer?.backgroundColor = NSColor.purple.cgColor
self.roundedCorners(on: self)
// add xib to custom NSView subclass
self.addSubview(self.view)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
}
The corresponding ViewController:
class View4Controller: NSViewController {
override func loadView() {
print("View4Controller.loadView")
self.view = NSView()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
print("View4Controller.viewDidLoad")
self.view = View4()
}
}
The idea of an outlet is to have a reference to an object that is outside of your code created. The concept is great for prototyping, but tends to become hard to manage as a project grow.
If you class is the class, then it can refer to itself. („self“ in swift or „this“ in c++) You don't need an outlet in this case.
The outlet is normally used by controller that need to maintain the view. The concept is a alternative to creating and configuring the view manually.
I setup UILabel appearance in my app delegate using:
UILabel.appearance().textColor = UIColor.white
I also have a custom UIView subclass that contains a UILabel along with some other elements (omitted here):
#IBDesignable
class CustomView: UIView {
private let descriptionLabel = HCLabel()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
private func setup() {
self.descriptionLabel.textColor = UIColor.black
// ... other things not related to descriptionLabel
}
}
If I instantiate CustomView in a storyboard, everything works just fine. If, however, I instantiate it in code, the descriptionLabel is white (appearance color), not black (the color I set). What's going on here? The way I understood it was that if I set a custom color, the appearance color will not be used.
What you're experiencing is simply a matter of the exact timing with which the UIAppearance proxy applies its settings to a new UIView. When are we to suppose it does this? It can't possibly do it before init, because init is the first thing that happens in the life of the UIView. Thus, the order of events is like this:
override init(frame: CGRect) {
super.init(frame: frame)
setup() // black
}
// and some time later, UIAppearance proxy comes along and sets it to white
So your goal is to call setup pretty early in the life of the label — and certainly before the user ever has a chance to see it — but not so early that the UIAppearance proxy acts later. Let's move the call to setup to a bit later in the life of the label:
// some time earlier, UIAppearance proxy sets it to white
override func didMoveToSuperview() {
setup() // black
}
Now we're acting after the appearance proxy has had a chance to act, and so your settings are the last to operate, and they win the day.
We remain in ignorance of how early we could move the call to setup and still come along after the appearance proxy setting has been obeyed. If you have time, you might like to experiment with that. For example, willMoveToSuperview is earlier; if you call setup there (and not in didMoveToSuperview), does that work? Play around and find out!
I have a slider:NSSlider and valueLabel:NSTextField, and I'm wondering what's the proper way to make it accessible for VoiceOver users.
First I connected a send action for slider to sliderChanged function to update valueLabel.
valueLabel.stringValue = String(slider.integerValue)
VoiceOver reads the label correctly, but it reads the slider in percentage. To fix this, I changed sliderChanged function to setAccessibilityValueDescription.
slider.setAccessibilityValueDescription(String(slider.integerValue))
Now VoiceOver correctly reads the value for the slider. However, it sees both valueLabel and slider, so it's redundant.
I tried valueLabel.setAccessibilityElement(false), but VoiceOver doesn't seem to ignore.
Could someone advise what would be the proper way to implement this? Thanks!
The best way to do this is to create a custom "ContainerView" class (which inherits from UIView) that contains the label and the slider, make the ContainerView an accessibilityElement, and set its accessibilityTraits to "adjustable." By creating a ContainerView that holds both the valueLabel and the slider, you remove the redundancy that is present in your current implementation, while not affecting the layout or usability of the slider/valueLabel for a non-VoiceOver user. This answer is based on this video, so if something is unclear or you want more in-depth info, please watch the video!
Setting a view's UIAccessibilityTraits to be "Adjustable" allows you to use its functions accessibilityIncrement and accessibilityDecrement, so that you can update whatever you need to (slider, textfield, etc). This trait allows any view to act like a typical adjustable (without having to add UIGestureRecognizers or additional VoiceOver announcements).
I posted my code below for convenience, but it is heavily based on the video that I linked to above. (I personally am an iOS developer, so my Swift code is iOS-based)
Note -- I had to override the "accessibilityValue" variable -- this was to make VoiceOver announce changes in the slider whenever the user swiped up or down.
My ContainerView class contains the following code:
class ContainerView: UIView {
static let LABEL_TAG = 1
static let SLIDER_TAG = 2
var valueLabel: UILabel {
return self.viewWithTag(ContainerView.LABEL_TAG) as! UILabel
}
var slider: UISlider {
return self.viewWithTag(ContainerView.SLIDER_TAG) as! UISlider
}
override var accessibilityValue: String? {
get { return valueLabel.text }
set {}
}
override var isAccessibilityElement: Bool {
get { return true }
set { }
}
override var accessibilityTraits: UIAccessibilityTraits {
get { return UIAccessibilityTraitAdjustable }
set { }
}
override init(frame: CGRect) {
super.init(frame: frame)
valueUpdated()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
valueUpdated()
}
func valueUpdated() {
valueLabel.text = String(slider.value)
slider.sendActions(for: .valueChanged)
}
override func accessibilityIncrement() {
super.accessibilityIncrement()
slider.setValue(slider.value + 1, animated: true)
valueUpdated()
}
override func accessibilityDecrement() {
super.accessibilityDecrement()
slider.setValue(slider.value - 1, animated: true)
valueUpdated()
}
}
Hope this helps!
I'm porting some code from Objc to Swift. And struggling with Swift's initialization life cycle. From a simplified point of view, there are 4 players I'm trying to bring into play here:
An object called a Program. This is my main top level model object at this point. The remaining 3 players all want access to an instance of him.
A ProgramEditController, painted in my main Main.storyboard. He's responsible for instantiating an initial Program, which cannot be done directly as a property initializer.
A top level custom UIView subclass, called ProgramTimelineView. Painted in the Main.storyboard, manages a variety of specialized subviews. Linked to a property of my ViewController. Has properties for the it's subviews as well. It wants access to the Program, so it can do layout and pass it on to subviews.
A particular subview of ProgramTimelineView called ProgramGridView. These are not painted in the XCode canvas tool, but directly instantiated by the containing ProgramTimelineView. It wants access to the Program. Uses it to do his custom drawRect.
Here's the relevant code for my Controller:
class ProgramEditController: UIViewController {
// MARK: - Variables
#IBOutlet var timelineView:ProgramTimelinesView!
var site = Site()
var program:Program!
// MARK: - Initialize
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// set up the new program
self.program = self.site.newProgram()
// get it into our top view before it starts drawing
self.timelineView.program = self.program
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) // Why does Swift make me have a redundant thing here?
}
}
And for ProgramTimelinesView:
class ProgramTimelinesView: UIView {
// MARK: - Variables
var gridView = ProgramGridView()
var program:Program! {
didSet {
self.gridView.program = self.program
}
}
// MARK: - Initialization
func addGridView() {
self.gridView.alpha = 0.0
self.gridView.opaque = false
self.addSubview(self.gridView)
}
func commonInit() {
self.addGridView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
}
And finally for the ProgramGridView:
class ProgramGridView: TimeAxisView {
// MARK: - Variables
var program:Program!
override func drawRect(rect: CGRect) {
// access self.program in here
}
}
What I thought would happen:
ProgramEditController.init(nibName...) would fire first.
The super call would cause my ProgramTimelineView.init(coder) to fire.
ProgramTimelineView instance would first call the gridView initializer setting it to a new ProgramGridView view
The remainder of ProgramTimelineView.init(coder) would run, which would add the gridView into the view tree.
Control would return to the ProgramEditController.init(nibName) initializers. The controller's program property would be populated.
The bound timelineView would have its program property set.
ProgramTimelineView would in turn set the program property of the gridView.
What seems to happen though, between steps 4 and 5, is that a drawRect() happens. That causes a seg fault, because the gridView's program has not been set yet! But why is it issuing drawRect()'s at this point? I thought that wouldn't happen before all of the initializers had fired. But clearly some side affect is occurring. What is the correct pattern/idiom to employ to avoid this? I really would rather not turn all of the program! into program? and then put let/guards every where.
There turned out to be a faulty assumption in my original premises (usually the case).
UIViewController.init(nib...) is NOT called when creating from interface builder assets. But local variables linked in interface builder (implicitly wrapped) are not set/realized at the point of init(coder) either. The correct approach required two adjustments:
Move the setup of the program var to the init(coder) initializers:
e.g.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.program = self.site.newProgram()
}
Forward that to the view in a viewDidLoad() override.
e.g.
override func viewDidLoad() {
super.viewDidLoad()
self.timelineView.program = self.program
}
I'm trying to create a reusable UIView in Swift that I can plug into my Storyboard view controllers. My key issue right now is that the reusable UIView "widget" doesn't fully fit into the UIView box in the storyboard. I followed this tutorial to set up the reusable UIView widget
Created a subclass of UIView and a corresponding .xib -- and connected these:
import UIKit
class MyWidgetView: UIView {
#IBOutlet var view: UIView!;
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil);
self.addSubview(self.view);
}
}
In the XIB, which is the interface file corresponding to the code above, I used UIView with Freeform size under the Simulated Metrics, and Scale to Fill under View mode.
In the main storyboard, I added a UIView block (same rectangular shape) and changed the Class to MyWidgetView
It works, but the components I created in the XIB look squished in the actual app, despite the fact that I used layout constraints in both the XIB and also the main storyboard.
See the screenshot. The pink part isn't supposed to appear, since that is just a color of the UIVIew on the main storyboard that I added to test the sizing. That UIView is actually MyWidgetView (after I changed the class in step 3. So in theory, since MyWidgetView == the UIView on the main storyboard, and that UIView has constraints that make it rectangular in the superview, then why is my widget squished? The blue part below should extend all the way right.
The actual view hierarchy loaded from the nib file in your code is added via
self.addSubview(self.view). So, the frame of your self.view actually has no relationship with its parent, i.e. MyWidgetView.
You may choose either adding layout constraints through code or just setting its frame after being added as a subview. Personally, I prefer the latter. In my experiment, the following is what works for me. I am using Xcode 6.4, which I think is not the same one as yours.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let nibsView = NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil) as? [UIView] {
let nibRoot = nibsView[0]
self.addSubview(nibRoot)
nibRoot.frame = self.bounds
}
}
Alternatively the variable frame can be overridden. This code worked for me when CardImgText was set to files owner for the view.
class CardImgTxt: NSView {
#IBOutlet var view: NSView!
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NSBundle.mainBundle().loadNibNamed("View", owner: self, topLevelObjects: nil)
addSubview(view)
}
}
if you are more interested in efficiency than real time updating. Then replace :
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
with:
override func viewDidEndLiveResize() {
view.frame = bounds
}