In my storyboard I've a ViewController linked with a class named MyViewController.
In this view, there are a lot of #IBOutlet and #IBAction and I would know if it's possible to decompose this ViewController with separated classes which contains #IBOutet and #IBAction.
For example:
class MyViewController
{
#IBOutlet var button1 : UIButton!
#IBOutlet var button2 : UIButton!
...
#IBOutlet var button10 : UIButton!
#IBAction func clickOnButton1(sender : AnyObject!)
{
...
}
#IBAction func clickOnButton2(sender : AnyObject!)
{
...
}
...
#IBAction func clickOnButton10(sender : AnyObject!)
{
...
}
}
Will be...
class MyViewController
{
let buttonsManager = ButtonsManager()
}
class ButtonsManager
{
#IBOutlet var button1 : UIButton!
#IBOutlet var button2 : UIButton!
...
#IBOutlet var button10 : UIButton!
#IBAction func clickOnButton1(sender : AnyObject!)
{
...
}
#IBAction func clickOnButton2(sender : AnyObject!)
{
...
}
...
#IBAction func clickOnButton10(sender : AnyObject!)
{
...
}
}
But the problem is, how can I link buttons IBOutlet and IBAction to ButtonsManager class because the owner of the view inside my Storyboard is MyViewController class?
Thanks!
Disclaimer: I don't know if this is a recommended way of doing this, but I thought it was an interesting question. Either way, I came up with this solution, which at least works. Decide for yourself if it's something you want to do.
First have your ButtonsManager be a subclass of NSObject. That way we can make an instance of ButtonsManager be an IBOutlet itself.
class ButtonsManager: NSObject {
#IBOutlet var button1 : UIButton!
#IBAction func clickOnButton1(sender : AnyObject!) {
print("hello world!")
}
}
I added an outlet for the button, and one action which prints "hello world!". Essentially, you would move all your #IBOutlets and #IBActions into this class.
In your view controller class, add a single #IBOutlet of type ButtonsManager:
class MyViewController {
#IBOutlet var buttonsManager: ButtonsManager?
// rest of class
}
What remains is to hook things up in Interface Builder:
Add a generic "Object" item from the Object Library to the scene.
In the Identity Inspector, set its custom class to "ButtonsManager"
Select the View controller in the scene (e.g. MyViewController) and go to the Connections Inspector.
Make a connection from the buttonsManager Outlet to the ButtonsManager object in the scene
Select the ButtonsManager object again in the scene and go to the Connections Inspector.
You'll find all the outlets and received actions here. Hook them up to your view like you would normally. I.e. make connections by dragging from the outlet/action to the respective view elements.
Compile!
EDIT:
A better solution would probably be to have a reference to the view controller in the ButtonsManager class, rather than the other way around. That would provide much easier access to the view controller class from your IBAction methods. As a bonus, you would have no #IBOutlet keywords in your view controller class.
Remember to change the connections in Interface Builder. That is, remove the connection on MyViewController, and add a connection on ButtonsManager from the myViewController outlet to the MyViewController object in the scene.
class MyViewController {
var testString = "Hello world!"
func testMethod() {
print("Printing some other string here!")
}
// rest of class
}
class ButtonsManager: NSObject {
#IBOutlet var myViewController : MyViewController!
#IBOutlet var button1 : UIButton!
#IBAction func clickOnButton1(sender : AnyObject!) {
print(self.myViewController.testString)
self.myViewController.testMethod()
}
}
Related
I have an onboarding user flow:
Name -> Age -> Gender
Each of the screens shares the same structure:
Question (top)
Input (middle)
Continue (bottom)
I have a class OnboardingHelper.swift that creates a class to set the question box and continue button:
class UserOnboardingHelper{
var text: String
var questionbox: UIView
var viewController: UIViewController
var continueButton: UIButton
init(text: String, questionbox: UIView, viewController: UIViewController, continueButton: UIButton){
self.text = text
self.questionbox = questionbox
self.viewController = viewController
self.continueButton = continueButton
}
func setQuestionBox(){
//sets question box
}
func setContinueButton(){
//sets continue button
enableContinueButton()
addContinueButtonPath()
}
func enableContinueButton(){
//enables continue button
}
func disableContinueButton(){
//disables continue button
}
func addContinueButtonPath(){
//sets path of continue button based on which view
}
}
In each of the onboarding ViewControllers I am setting the class in ViewDidLoad():
class NamePageViewController: UIViewController, UITextFieldDelagate {
#IBOutlet weak var questionbox: UIView!
#IBOutlet weak var continueButton: UIButton!
#IBOutlet weak var inputLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let namePageSettings = UserOnboardingHelper(text: "What is your name", questionbox: questionbox, viewController: self, continueButton: continueButton)
namePageSettings.setQuestionBox()
namePageSettings.setContinueButton()
inputLabel.delegate = self
if nameIsFilled {
namePageSettings.enableContinueButton()
} else{
namePageSettings.disableContinueButton()
}
}
}
The issue is that in the ViewController I textFieldDidEndEditing() function which needs to call the namePageSettings class from viewDidLoad()
func textFieldDidEndEditing(_ textField: UITextField){
if (textField.text?.empty)!{
//I want to call disableContinueButton() from UserOnboardingHelper
} else {
//I want to enable enableContinueButton() from UserOnboardingHelper
}
}
Trying to understand if:
The overall approach is correct and if not, what's the best way
If the above approach is in the right direction, how should disableContinueButton() and enableContinueButton() be called?
Thanks in advance! Sorry if the approach is really dumb - I'm still trying to wrap my head around classes.
You can have the view controller have a weak reference to the onboarding helper, so you can still call helper methods without creating a retain cycle.
In NamePageViewController, add a property:
weak var userOnboardingHelper: UserOnboardingHelper?
Then, in UserOnboardingHelper's initializer, add:
self.viewController.userOnboardingHelper = self
You can now call the onboarding helper's methods in the view controller:
userOnboardingHelper.disableContinueButton()
userOnboardingHelper.enableContinueButton()
I have a custom class which inherits from UIView inside of which is a UIImageView and button. I'm adding them to my storyboard as UIView and let's say there are two views. I have a delegate and a function which executes when the button is pressed and it works for both UIViews.
My problem is that I don't know how to tell which one was clicked. I need to pass it to another VC and then display suitable image in the UIView which was clicked. I've been thinking of setting ids or something but it does not seem like a very scalable idea.
And I cannot simply add #IBAction because the view is UIView as a whole.
Inside the ViewController I've added them as #IBOutlets:
#IBOutlet weak var view: CustomImageView!
#IBOutlet weak var view2: CustomImageView!
EDIT:
I'm pretty close to creating reusable and scalable solution - I'm just assigning the tag to every object and than passing it and retrieving it. Problem I've encountered is that I would like to access properties of a tag. I didn't find any solution on how to do it.
Solution with delegates
You could add a custom delegate protocol for your custom view
protocol CustomViewDelegate {
func didSelectCustomView(_ view: CustomView)
}
class CustomView: UIView {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myButton: UIButton!
weak var delegate: CustomViewDelegate?
#IBAction func buttonPressed(sender: UIButton) {
delegate?.didSelectCustomView(self)
}
}
And in your ViewController you have to check from which view to delegate call comes:
class ViewController: UIViewController {
#IBOutlet weak var view1: CustomView!
#IBOutlet weak var view2: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
view1.delegate = self;
view2.delegate = self;
}
}
extension ViewController: CustomViewDelegate {
func didSelectCustomView(_ view: CustomView) {
if view == view1 {
// view1 clicked
} else {
// view2 clicked
}
}
}
Solution with blocks
Instead of a delegate, you could add an optional block that is executed if the button in the view was clicked:
class CustomView: UIView {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myButton: UIButton!
var clicked: ((CustomView) -> Void)?
#IBAction func buttonPressed(sender: UIButton) {
clicked?(self)
}
}
And in your ViewController, you can add the block to your views:
class ViewController: UIViewController {
#IBOutlet weak var view1: CustomView!
#IBOutlet weak var view2: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
view1.clicked = { view in
}
view2.clicked = { view in
}
}
}
I am beginner in iOS development and I have implemented following screen using UICollectionView :
CollectionViewCell File Code is:
import UIKit
class EventCell: UICollectionViewCell{
var classEvent: Event?
#IBOutlet weak var eventTitle: UILabel!
#IBOutlet weak var eventTeams: UILabel!
#IBOutlet weak var eventTime: UILabel!
#IBOutlet weak var eventTeamOneImage: UIImageView!
#IBOutlet weak var eventTeamTwoImage: UIImageView!
#IBOutlet weak var leaderboardButton: UIButton!
var datasourceItem: Any?{
didSet{
guard let event = datasourceItem as? Event else { return }
classEvent = event
eventTitle.text = "Match \(event.matchNo) (\(event.matchStage))"
eventTeams.text = "\(event.teamOne.nameAttr) vs \(event.teamTwo.nameAttr)"
eventTime.text = "\(event.getEventLockTimeAsString())"
eventTeamOneImage.loadImageUsingCache(withUrl: event.teamOne.flagPhoto)
eventTeamTwoImage.loadImageUsingCache(withUrl: event.teamTwo.flagPhoto)
leaderboardButton.addTarget(self, action: #selector(handleLeaderBoardClick), for: .touchUpInside)
}
}
#IBAction func leagueButton(_ sender: Any) {
}
weak var delegate: HomeControllerDelegate?
func handleLeaderBoardClick() {
if let matchId = classEvent?.id {
print(matchId)
delegate?.clickOnLeaderBoard(matchId: matchId)
}
}
}
Now on click on Leaderboard button(icon with 1,2,3) I would like to open new LeaderBoard Controller and pass matchId which is classEvent.id
How can I pass values to the new controller? And what is the best way to do that.
You can pass the match Id via segue:
In LeaderBoard Controller set a property:
var matchId:Int?
Set a segue between the controller and add an identifier:
On Click leaderboard button:
self.performSegueWithIdentifier("yourSegueIdentifier", sender: self)
Add the segue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue,identifier == "yourSegueIdentifier" {
let destinationVC = segue.destinationViewController as LeaderbardController
destinationVC.matchId = classEvent.id
}
}
}
Three easy steps to get what u want:
Make a BaseViewController class a subclass of UiViewController. This class would be the alternate of UiViewcontroller in your project,it means while creating any viewcontroller BaseViewController will be the parent class.
Declare a variable in BaseViewController.e.g- var data: Any?
Then while moving from a viewcontroller to another , simply assign any type of data to that variable declared in BaseViewController.
And in any lifecycle method of your new viewcontroller you will get that data using self.data.
This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I'm having a problem, I have a UIButton in a UIViewController class and I want to enable that button after an animation that happens in a UIView class that is in another file.
class MainViewController: UIViewController {
#IBOutlet weak var nextButton: UIButton!
#IBAction func nextButtonPressed(sender: UIButton) {
nextButton.enable = false
}
}
When I try to call the nextButton from the viewController class after the animation is done I get this error:
EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP, subcode = 0x0)
I get the error on the line where I set the nextButton enable to true.
class CustomView: UIView {
var vc = MainViewController()
func animationEnded() {
vc.nextButton = true
}
}
I don't have a clue what I'm missing and I would appreciate some help. Thanks
You encounter an error because in your CustomView you create a new MainViewController, you're not using the one initialized from the storyboard. That new MainViewController doesn't have any of its properties initialized, so nextButton is nil, hence the crash when you try to access it.
What you want to do is notify your controller from the view that the animation has ended so that the controller can update the button (since the controller owns the button). The standard way to do this in Cocoa is to use the delegate pattern like so:
class MainViewController: UIViewController, CustomViewDelegate
{
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var customView: CustomView!
#IBAction func nextButtonPressed(sender: UIButton) {
self.nextButton.enabled = false
}
override func awakeFromNib() {
super.awakeFromNib()
self.customView.delegate = self
}
func customViewAnimationDidEnd(customView: CustomView) {
self.nextButton.enabled = true
}
}
protocol CustomViewDelegate : class
{
func customViewAnimationDidEnd(customView: CustomView)
}
class CustomView: UIView
{
weak var delegate: CustomViewDelegate? = nil
func animationEnded() {
self.delegate?.customViewAnimationDidEnd(self)
}
}
In this implementation the controller is the view delegate and gets notified when interesting events happen in the view (like a particular animation ending).
Make a delegate in your UIView that tells when it should hapen
protocol CustomViewDelegate {
func pushThatButton()
}
in CustomView class put this:
weak var delegate: CustomViewDelegate?
then
func animationEnded() {
delegate.pushThatButton()
}
and in UIViewController
class MainViewController: UIViewController, CustomViewDelegate {
and implement delegate ofc
func pushThatButton()
nextButton.sendActionsForControlEvents(.TouchUpInside)
}
almost forget, do an outlet to your view in viewController and setup delegate! in viewDidLoad() or when you will find this best
customViewOutlet.delegate = self
I have a UITextField on one ViewController and I want to be able to have a Label on the second ViewController = whatever the user enters.
However, I get an error when trying to do this. I know Swift doesn't use import so I'm not sure what to do.
// First View Controller
#IBOutlet weak var textOne: UITextField!
// Second View Controller
#IBOutlet weak var theResult: UILabel!
theResult.text = textOne.text
Error: Unresolved Identifier
It looks like that you in reality want to just access the text from the UITextField and not the field itself. So you should send the text to the second ViewController from your first one by using prepareForSegue.
But before, you have to set the name of your segue so that Swift knows which data to send:
As you see, we name the segue segueTest.
So now we can implement the prepareForSegue-method in the FirstViewController and set the data which should be sent to the second.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueTest") {
var svc = segue!.destinationViewController as secondViewController;
svc.theText = yourTextField.text
}
}
That you can do that, you have to set a variable in your SecondViewController of course:
var theText:String!
You need to pass a reference to your FirstViewController instance to the SecondViewController instance at prepearForSegue...
Then do something like this
//FirstViewController
#IBOutlet weak var textOne: UITextField!
func getTextOne () -> String? {
return textOne.text
}
//SecondViewController
#IBOutlet weak var theResult: UILabel!
var firstViewControllerInstance: FirstViewController?
func showResult () {
theResult.text = firstViewControllerInstance?.getTextOne()
}
And you are done