create IBOutlet / IBAction & connect it with UI in a custom class - ios

In my ViewController, I have two UIImageView which have IBOutlet connection to UI :
class ViewController: UIViewController {
#IBOutlet weak var img1: UIImageView!
#IBOutlet weak var img2: UIImageView!
...
Now, I want to refactor my project to have a dedicated class ImageManager handles the images. So, the ViewController becomes like this :
class ViewController: UIViewController {
var imgMgr: ImageManager!
override func viewDidLoad() {
super.viewDidLoad()
imgMgr = ImageManager()
}
}
I want the ImageManager class to declare the two UIMageViews the same way as that in ViewController and make the IBOutlet connection with UI, but looks like it doesn't work in this way:
class ImageManager : NSObject {
#IBOutlet weak var img1: UIImageView!
#IBOutlet weak var img2: UIImageView!
...
}
Is there any way to make a custom class holding the IBOutlet & IBAction which connects to interface builder UI components (instead of doing it in ViewController)?

I don't think you should do this. You should just manage your images in your custom UIImage class using for example:
//inside the function awakFromNib
self.layer.cornerRadius = 5.0 //will make all the images' corner radius of elements assigned to this custom class rounded
self.clipsToBounds = true
Then you will drag the image outlets as usual to the view controller, but instead of setting its class to "UIImage", set it to the custom class name
#IBOutlet weak var someImg: CustomImgClass!

Make ImageManager subclass of UIView.
Create a new View under your ViewController and change it class name to ImageManager.
Drag Two UIImageViews to your ImageManager.
See the out Outlets of ImageManager View and connect them to the newly created View.
In your viewWillAppear just add the ImageManager as subview.

Related

Loading UIView from XIB using Storyboard - view's properties are nil [duplicate]

This question already has answers here:
Load view from an external xib file in storyboard
(11 answers)
Closed 3 years ago.
I've created a view:
import UIKit
class TestNotWorkingView: UIView {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
}
And connected with the corresponding XIB:
Then, I've added the view to the UIViewController on the Storyboard:
class ViewController: UIViewController {
#IBOutlet weak var testNotWorkingView: TestNotWorkingView!
override func viewDidLoad() {
super.viewDidLoad()
print(testNotWorkingView)
print(testNotWorkingView.label1)
}
}
When I run the app, only the parent view appears. No labels are visible:
Parent view:
Optional(TestProject.TestNotWorkingView: 0x7fb165e04610; frame = (0
0; 414 818); autoresize = RM+BM; layer = CALayer: 0x600001f254e0)
Querying one of the labels
nil
I've already set the TestNotWorkingView as the view's class and file owner.
I assume since the views are not present on the storyboard, they're not being created and linked at runtime.
How can I use views created in XIBs inside a ViewController created in Storyboard? Is it possible at all?
Test Project that fully reproduces the issue
Using view designed in xib can be a bit tricky, you need to add this code to your TestNotWorkingView class:
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else {
return self
}
return Bundle.main.loadNibNamed("TestNotWorkingView", owner: nil, options: nil)?.first
}
EDIT: also, you should leave File's Owner Empty, and connect outlets only to TestNotWorkingView

Override #IBoutlet property with different type of compatible class

I have #IBOutlet var heading of type UILabel in base class, now I subclassed this and in the new class I want to have #IBoutlet heading of type MyLabel (let's say I have a class MyLabel subclassed from UILabel). So the question is: how to override the same property to different compatible class?
There is no way to override property with different type. You can change a class of this property in the Interface Builder and make another property with needed class:
#IBOutlet override var label: UILabel!
private var myLabel: MyLabel? {
return label as? MyLabel
}

How to get the UIVIewController's IBOutlet list programmatically

I am building one framework. I need to get the IBOutels list from UIViewcontroller through code. I have written InterfaceOutletsReadable protocol. If the framework user conforms this protocol I have to read the list of IBOutlets from ViewController.
protocol InterfaceOutletsReadable {
///Read the outlets objects
func readOutlets()
}
extension InterfaceOutletsReadable {
//TODO:- Stuck at this stage. Here I have to read the viewcontroller IBOutlets
}
class HomeViewController: InterfaceOutletsReadable {
#IBOutlet weak var userNameTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var errorLabel: UILabel!
}
Edit: I don't want to get the list from IBOutlet Collection. Is there any way to get all outlets programmatically?
you can take IBOutletCollection of one textField and connect to all others.It maintain Array of Outlet and You Can access through index.
#IBOutlets exist only at design time, there are no ones even at compile time. So it’s imposible to get all outlets in the way you want. Outlets have no difference with other variables.
If you need list of the outlets you may implement readOutlets() in your HomeViewController and return Array consist any variables you want.

Swift 2.2 - Updating UILabel throws "unexpectedly found nil while unwrapping an optional value"

I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().

Override IBOutlet properties from super class?

I would like to create a super class in a framework that modifies IBOutlet properties. However, I would like a subclass to be connected to the storyboard, since I don't want to connect the controls to the class in the framework.
For example, the super class in my framework looks like this:
public class MySuperDetailViewController: UIViewController {
#IBOutlet public weak var titleLabel: UILabel?
#IBOutlet public weak var dateLabel: UILabel?
#IBOutlet public weak var contentWebView: UIWebView?
...
}
Then in the subclass, I would like to control-drag controls onto the subclass. So I have to expose those properties by overriding. I'm trying to do this but it won't allow me:
class MyDetailViewController: MySuperDetailViewController {
#IBOutlet weak var titleLabel: UILabel?
#IBOutlet weak var dateLabel: UILabel?
#IBOutlet weak var contentWebView: UIWebView?
}
The error I get is: Cannot override with a stored property 'titleLabel', 'dateLabel', and 'contentWebView'.
How can I do this or better approach to this?
Don't try to recreate the variables in the subclass; the IBOutlet variables still exist. You can still connect them inside of Interface Builder in a number of ways.
Utilites (right panel) -> Connection Inspector - drag from the list of IBOutlets.
Document Outline (left panel) - drag from MyDetailViewController
Drag from the yellow circle when you have the UIViewController selected in Interface Builder
Note: All UIViewController subclasses inherit an IBOutlet named view; this property already exists in Interface Builder even though you can't click + drag to connect it.
All sub-classes get direct access to the outlets in the super-class. So you can just change the properties of the outlets direcly in the respective sub-class. Like this:
class MySubClass: MySuperClass {
override func viewDidLoad() {
super.viewDidLoad()
mySuperClassOutlet.text = "This overrides superclass text"
}
}

Resources