Access properties from subview to ViewController Swift - 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
}

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.

Avoid boilerplate code to setup UI in viewDidLoad

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
}
}

#IBDesignable class with .xib-File not rendering in Interface Builder

I have a CustomClass.swift and a CustomClass.xib. I want XCode's Interface Builder to render views of class CustomClass using the provided .xib-File. Just like it does when I run the app.
I am using XCode 8.3.2 (8E2002)
CustomClass.swift
import UIKit
#IBDesignable class forceInterface: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("forceInterface", owner: self, options: nil)
self.addSubview(view)
view.frame = self.bounds
}
#IBOutlet var view: UIView!
#IBOutlet weak var label: UILabel!
#IBOutlet weak var progressView: UIProgressView!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
}
CustomClass.xib
File owner is set to CustomClass. It contains a UIView that contains a UISwitch and a UILabel.
The problem is that, eventhough I have #IBDesignable in my CustomClass.swift, it isn't rendering in the InterfaceBuilder. I am placing a UIView there and setting its class to CustomClass:
As you can see, it even says Designables: Up to date in the inspector. But I am not seeing anything.
When running the app everything works as expected!
Why are you doing this
self.addSubview(view)
view.frame = self.bounds
You should assign the views' frame before you add it as a subview to its superview
view.frame = self.bounds
self.addSubview(view)
I am not sure why you view is not loading even though you have assigned it as IBDesignable. I normally use these steps to troubleshoot.
Close and reopen Xcode
Clean Xcode
Wait for a while, normally around a minute or so, and you will see the view refresh/update itself, displaying your custom drawn content

loadnibnamed not loading subviews

I am trying to load a xib filed called "AddProgramView" using loadnibnamed when a user presses a button. I created the xib in storyboard, placed various subviews within the xib view, and hooked up outlets to a corresponding swift file named AddProgramView. When I load the xib, none of the subviews appear. They are all missing. Help? Here's my code
Within my main view controller:
#IBAction func addProgram(sender: UIButton) {
if let _ = addProgramView {
addProgramView!.frame = CGRectMake(0, self.window!.frame.origin.y + self.window!.frame.height, self.window!.frame.width, CGFloat(325))
}
else {
let addProgramViews = NSBundle.mainBundle().loadNibNamed("AddProgramView", owner: self, options: nil)
addProgramView = addProgramViews.first as? AddProgramView
addProgramView!.frame = CGRectMake(0, self.window!.frame.origin.y + self.window!.frame.height, self.window!.frame.width, CGFloat(325))
window?.addSubview(addProgramView!)
}
window?.bringSubviewToFront(addProgramView!)
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.addProgramView!.frame.origin.y -= CGFloat(325)
}, completion: nil)
}
The AddProgramView.swift file:
class AddProgramView: UIView {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var instructionsLabel: UILabel!
}
First check, if you have your class name declared in the view itself, not in the file owner.
If not, do it. Then try it again.
Another option is to create IBOutlet in your AddProgramView.swift
#IBOutlet var contentView: UIView!
and remove the class name from the view and add it to File's Owner.
Connect contentView by right-clicking at the File's owner:
Then create init methods like this:
class AddProgramView: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
NSBundle.mainBundle().loadNibNamed("AddProgramView", owner: self, options: nil)
self.addSubview(self.contentView)
self.contentView.frame = self.bounds
}
}
And now you can use the AddProgramView wherever you want. Either by init(frame:)
let addProgramView = AddProgramView.init(frame: CGRectMake(0, 0, 320, 30))
or you can use them directly inside storyboard by adding UIView and set them class to AddProgramView (No extra line of code, storyboard will call init(coder:) for you)
I've created example project for you - CustomView project
Double-check that you don't have a size class specified in your xib file. It should say Any/Any at the bottom of the editor window.