Avoid boilerplate code to setup UI in viewDidLoad - swift

i am just wondering how did you properly setup your UI in your IOS developments with Swift. Generally, I feel like I need to put a lot of statements in viewDidLoad lifecycle method of a view controller to customize UI elements. I know that I can use storyboard to help to setup those UI elements but sometimes we need to make some adjustments programmatically. Those adjustments resulting in a huge and boilerplate code in viewDidLoad. So, how do you handle this ? Did your use extensions only for the UI part ? Specific classes ? How you can clearly separate UI from logic ?

Make a custom view for it!
If you find yourself writing a lot of this kind of code:
myView.someProperty1 = someValue1
myView.someProperty2 = someValue2
myView.someProperty3 = someValue3
myView.someProperty4 = someValue4
myView.someProperty5 = someValue5
myView.addSubView(subView1)
myView.addSubView(subView2)
myView.addSubView(subView3)
...
and the values that you give the properties are all independent of the view controller, it might be time to create a custom view.
Here is an example:
Create an xib file for your view, and name it the same name as your custom view. You will be adding the subviews of your custom view and all the constraints you need here.
And then you can do something like this:
#INDesignable // add this if you want to see your view drawn on the storyboard!
class MyCustomView: UIView {
#IBOutlet var subView1: UIImageView!
#IBOutlet var subView2: UITextField!
#IBOutlet var subView3: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let view = viewFromNibForClass()
view.frame = bounds
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
// set up your view here...
// set all the properties and stuff
}
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: MyCustomView.self)
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}

Related

Where to ctrl-drag IBOutlets, view class or ViewController?

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.

How to create singleton UIView with xib for using one instance on different VCs?

I have a custom UIView subclass, which has xib. I need to use one instance on different ViewControllers in my App.
My custom view initialize like this:
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commitInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commitInit()
}
private func commitInit() {
contentView = loadFromNib(named: "ChartWithPoints")// Extension for UIView class, loading by name
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(contentView)
}
How to change this code for singleton and how use this class after changing?
Thanks for all answers.
I don't think you want a singleton, nor do you need to use one instance for each view controller. Managing ownership for the one view is completely unnecessary. The whole point of a class is to have multiple objects. You don't need to have a singleton.
For each view controller, keep doing what you are doing right now and instantiate a new custom view per controller.
To answer your question about using the subclass, change the view property to be the subclass instead of UIView:
#IBOutlet var contentView: SubclassView!
To read more about what problems singletons solve, take a look at the Wikipedia article on it.
Edit
Since you asked about what the model-view relationship would look like, I'll try to give a basic example.
Say, on your view, you want to display the user's name and hobbies. The singleton model class would look something like this:
class UserData {
static let singletonUserData = UserData()
var name: String
var hobbies: [String]
private init() {
self.name = "Bob Appleseed"
self.hobbies = ["Swimming", "Planting Apples", "Drawing"]
}
}
You will then be able to access the singleton via:
UserData.singletonUserData
In your view class, you may have some labels:
#IBOutlet var nameLabel: UILabel!
#IBOutlet var favoriteHobbyLabel: UILabel!
Then, after initialization, you can populate the labels using the singleton:
contentView.nameLabel.text = UserData.singletonUserData.name
contentView.favoriteHobbyLabel.text = UserData.singletonUserData.hobbies.first!
If you wanted to modify the singleton's properties, you could change them from anywhere:
UserData.singletonUserData.name = "John Appleseed"
import UIKit
import Foundation
class CustomView : UIView
{
#IBOutlet weak var titleLabel : UILabel!
// Singleton instance
static let shared : CustomView = CustomView.sharedInstanceFromNib(size: CGSize(width: 100, height: 100))
// You can modify argument list as per your need.
class private func sharedInstanceFromNib(size : CGSize)->(CustomView)
{
// This will connect your IBOutlet's with view's in Xib.
let view = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as! CustomView
view.frame.size = size
return view
}
}
NOTE - For first time when you use shared instance, It must be use on main thread, other wise Xcode's console will show some warning message.

UIView not resizing from xib file Swift

I have created an xib file with a UIView inside of it. I am using this view inside of my storyboard, and would like it to resize based on the content within it. When I explicitly set the height of the view in the storyboard, it is the same height as the XIB file. Each phone screen seems to have the same xib height and width as well, which causes the view to go off the screen, no matter what I set the constraints to be within the storyboard.
I have tried this to hide the view and set the height of the banner to 35
self.specialistBanner.pillView.isHidden = true
specialistBannerHeight.constant = 35
I have tried
specialistBannerHeight.constant = 35
to get the view to be smaller within the storyboard
I have also tried
self.specialistBanner.sizeToFit()
with no avail
XIB File for the specialist banner view
Label inside the xib file
Pill View within the xib
Storyboard file using the xib view inside of a view controller
class SpecialistBannerView: UIView {
#IBOutlet weak var bannerTitle: UILabel!
#IBOutlet weak var pillView: PillCollectionView!
var viewModel = SpecialistBannerViewModel()
// Initialize xib
let SpecialistBannerView = "SpecialistBannerView"
override func awakeFromNib() {
super.awakeFromNib()
self.translatesAutoresizingMaskIntoConstraints = false
}
override init(frame: CGRect) {
super.init(frame: frame)
initXib(view: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initXib(view: self)
}
func initXib(view: SpecialistBannerView) {
let viewToShow = Bundle.main.loadNibNamed(SpecialistBannerView, owner: self, options: nil)?[0] as? UIView ?? UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.sizeToFit()
view.addSubview(viewToShow)
view.frame = view.bounds
view.pillView.setUp()
view.sizeToFit()
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
viewModel.specialistBannerViewProtocol = self
}
Any help would be appreciated
I had your same problem. I found the answer in another question.
You have to override LayoutSubViews() in your custom view and set the bounds
override func layoutSubviews() {
super.layoutSubviews()
// we need to adjust the frame of the subview to no longer match the size used
// in the XIB file BUT the actual frame we got assinged from the superview
self.view.frame = self.bounds
}

Access properties from subview to ViewController Swift

how I can access the properties of my subview to viewcontroller.
example: If I have a #IBOutlet weak var loginBtn: UIButton! how can I access that to my view controller? I did a lot of things and tutorial in how to do that but still I didn't manage to make it work.
And also I'm confuse what's the best way to load xib? there's a lot of way that I saw in tutorial but I want to know also how they do it in production app.
protocol UserLoginDelegate {
func userDidLogin(status: Bool, message: String)
}
#IBDesignable class LoginWidget: UIView {
var loginDelegate: UserLoginDelegate?
var loginView: UIView!
var nibName: String = "LoginWidget"
let defaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
#IBOutlet weak var name: UITextField!
#IBAction func loginBtn(sender: AnyObject) {
// init
override init(frame: CGRect) {
super.init(frame: frame)
// set anything that uses the view or visible bounds
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// setup
setup()
}
func setup() {
loginView = loadViewFromNib()
loginView.frame = bounds
loginView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(loginView)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Controller
class AuthenticationViewController: UIViewController, UserLoginDelegate {
override func viewDidLoad() {
super.viewDidLoad()
loginWidget = LoginWidget(frame: CGRect(x: 0, y: 171, width: 375, height: 247))
authenticationSegment.setEnabled(true, forSegmentAtIndex: 0)
loginWidget.loginDelegate = self
self.view.addSubview(loginWidget)
// Do any additional setup after loading the view.
}
}
Just first, about SO: The goal here is to sort of have a crowd sourced knowledge base(an ontology!) that follows a pattern. Regular users like questions in the form of:
I'm confused about this. I want to accomplish this very specific thing. Here is my code that I've tried. I think this is the problem. Can you tell me how to do this properly or make it work?
So the in the end we get a long list of indexed searchable issues with direct solutions. These sort of wishy washy, non specific, multi-question, confusing posts tend to get downvoted, disregarded, told to rephrase, etc etc.
I'll address your questions:
If I have a #IBOutlet weak var loginBtn: UIButton! how can I access
that to my view controller? I did a lot of things and tutorial in how
to do that but still I didn't manage to make it work.
With a UIButton, or any UIControl subclass, you can drag an #IBAction into your view controller. Accomplish this by ctrl dragging from the storyboard button in question to your code(same way you made the outlet), and selecting Action from the drop down. Then you can optionally change the type of object sending this action (Is it a UIButton, or can anyone call this method, it just happens to be linked to the button), or you can change the control event that it it gets called for (ie is it when they touch DOWN on the button or touch up while the finger is still touching inside the button, this second is the default.)
More generally, if you have a complicated view that is not a control, you can always add a gesture recognizer to it(ie a UITapGestureRecognizer) and drag an IBAction from that, (drag the gesture recognizer onto the view, then from the left pane, ctrl drag from the gesture rec. to your code and select action again).
Finally, and this is more than you need currently, but if you have a view that must inform the view controller of something, or to tell the view controller(or any object) to do something like "send me data please", you can use the delegate pattern. Just briefly: You define a protocol to list the methods and variables needed by the protocol. You make your delegate object conform to this protocol by giving each method an implementation, then you put a variable in your view like
weak var delegate: MyProtocol?
The delegate pattern is important in Swift, and you should probably read up on it. You have probably worked with it if you've used a table view, for instance.
And also I'm confuse what's the best way to load xib? there's a lot of
way that I saw in tutorial but I want to know also how they do it in
production app.
Here's how I start every view that is a xib file's owner. I believe this is the proper way and would appreciate correction if this is wrong. This is swift 3, which you should be using at this point.. it's much better!
#IBOutlet var mainView: UIView!
// the above is the outlet from the xib's main view. which I always call main view
override init(frame: CGRect) {
super.init(frame: frame)
combinedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
combinedInit()
}
func combinedInit(){
Bundle.main.loadNibNamed("XibFileName", owner: self, options: nil)
addSubview(mainView)
mainView.frame = self.bounds
// more initialization stuff
}

Swift: Reusable UIView in storyboard and sizing constraints

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
}