Custom View (XIB) and IBAction - swift

I created a custom view (xib). This view is nothing more than an elaborate button (e.g. contains a label and icon). I want to handle click events exactly like the standard UIButton. That is, control-drag the button to the IBAction function that'll be called when button is clicked. From the storyboard, I want to be able to control-drag my custom UIView to the IBAction function that will handle the event someone taps my view. I don't want use UITapGestureRecognizer if I don't have to.

Just include a UIButton in your custom view. You can customize the button so that it has no words or design, and then you can place your label and icon, etc. on top of that button. Then you will be able to ctrl-drag from your button to an IBAction function as you want

Create a custom class for that Xib and then you use
class customButton: UIButton {
#IBOutlet weak var textLabel: UILabel!
#IBOutlet weak var imageView: UIImageView!
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
//Setup texts and images
}
}
Now you can use this class as customClass in your Xib and in turn use it as a customClass on the button you wish to use it on. Next, just drag an #IBAction as you wanted to. And you can reuse this button with other texts and images for other classes if you create an configure function inside that you can call whenever you want :).

Related

Swift: Need guidance if this is a good approach on making nested UIView in VC

Hi so right now I'm making some sort of camera app with some basic button like turn on flash, enable grid, etc.
The cameraViewController manages the view. And it has the UIView for previewing what to capture, and at the bottom, a UIView for the buttons, and shutter button.
Right now, this is how I approach it.
ShutterButtonUIView inside CameraEditorUIView, and CameraEditorUIView inside CameraViewController.
And my editor buttons (flash, grid, filters, etc) is of their own class. And I insert it into my view like so. That way, I promote separation of concern and my class won't be cluttered by all the many buttons.
let editorButtons = ActionButtonFactory.shared.editorButtons
for button in editorButtons {
// Insert to CameraEditorUIView
}
And example of one of my editor button class
class FilterAction: UIButton {
private let filter = ["paintbrush.fill"]
override init(frame: CGRect) {
super.init(frame: frame)
self.setImage(getIcon(), for: .normal)
self.addTarget(self, action: #selector(filterTapped), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc private func filterTapped() {
TapticHelper.shared.lightTaptic()
NotificationCenter.default.post(name: .presentFilterListVC, object: nil) //Not sure if this is a good approach
}
private func getIcon() -> UIImage {
return IconHelper.shared.getIconImage(iconName: filter[0])
}
}
This makes my line of code very neat and small, good. But is it a good approach? I can't do any view controller functions such as presenting any alert, instantiate another VC, and such from the UIView.
So let's say After pressing the shutter button I want to go to imageDetailVC, My approach is to use NotificationCenter observer pattern.
I'm not sure if this is a good approach or not. Or should I put everything under CameraViewController?
Also, another reason I have doubt on this approach is when I need to disable the rule of third grid for example, I need to make it a static so that my other UIView can access it.
ie: A button from another class
I'm not sure if this is the best approach, But I've figured that instead of using UIView, I used UIViewController. And add them as a child VC. That way, I can still access the parent without coupling with notifications and make it harder.
So if I was to instantiate a new VC, I just used self.parent.

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.

Make reusable component in Xcode storyboard

I have a specific situation but what I'm looking for is a generic solution. Currently I have a UIImageView that contains an image, a few labels, and multiple levels of constraints. I would like to configure this set of controls' properties once and reuse them inside of multiple controllers. Such that if I have to update this set, I would do it in one place and all the controller instances would get the change (sort of like how Sketch works with symbols).
You, sir, need a custom View!
My typical approach for this is to create an xib file, design the view I need, and create a class that subclasses UIView.
When you do this, you can assign the class of the xib File's Owner (in interface builder) and link up any #IBOutlets from the view to your custom class.
For the class, you'll need to implement a few methods. Here is an example custom view:
class LoadingView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var messageLabel: UILabel!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
setUpView()
}
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
setUpView()
}
func setUpView() {
self.view.layer.cornerRadius = 10.0
self.view.layer.masksToBounds = true
}
private func loadViewFromNib() {
let bundle = Bundle.init(for: self.classForCoder)
bundle.loadNibNamed("LoadingView", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}
}
You are required (pun intended) to implement the required init and the override init methods, and the other two are (kind of) optional. The loadViewFromNib is a convenience method that implements the logic to actually load the xib file from your app's bundle.
Don't forget to match the nib name with your xib file name! You'll thank me later. ;)
You can use this view in storyboards and use constraints, etc. by placing a regular old view and assigning its class to your custom class.
You can also play around with #IBDesignable to actually see your custom view in interface builder, though it tends to constantly reload and slow down Xcode unless you toggle a setting that I can't remember the name of right now (sorry!).
Enjoy!
What you want is not possible exactly in the way you describe it but there's a way to achieve the same result.
Create a subclass of UIView that will contain all the content you want, once you do that there are two options.
The first (and best, imo) option is to generate your layout with code when the view is initialized. This will allow you to add the view to other view controllers and it will initialize itself. The downside of this method is that you'll need to create the constraints with code.
The second option is to create a xib with your views and constraints and initialize your custom class from that xib. The downside of this is that you'll have to instantiate your view with code and place it in the view hierarchy yourself. You could create a container in the storyboard where you will add the view and pin it to the edges.

#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

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
}