Swift3 Tab Bar Controller - Single View Controller

Discussion in 'iOS Programming' started by craznar, Mar 11, 2017.

  1. craznar macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #1
    I'm trying to write a simple bar code scanning app using a hardware attachment to an iOS device.

    The restriction is that I can only have one view controller descending from the device events, but I need 3 or 4 tabbed views responding to the device.

    So in essence - I need two or more views with one view controller, and I can't work out how to do it.

    I created a stand alone app, embedded it in a tab bar, then added a second tab and assigned the view controller to it as well.

    I then create a label on each view, 'greenlabel' and 'bluelabel'.

    Layout here :
    [​IMG]

    The following is in my ViewController.swift:

    import UIKit
    class ViewController: UIViewController {

    @IBOutlet weak var GreenLabel: UILabel!
    @IBOutlet weak var BlueLabel: UILabel!

    override func viewDidLoad() {
    super.viewDidLoad()
    GreenLabel.text = "Green"
    BlueLabel.text = "Blue" // <--- crashes here because BlueLabel is nil
    // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
    }
    }


    How or what do I do to allow access to both views from the same code?

    Thanks.
     
  2. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    What do you mean by "The restriction is that I can only have one view controller descending from the device events"

    Each scene in your app will be matched to a single view controller. You need to understand the Model-View-Controller design. Most likely you need a Data layer object that represents the scanner and which sends events to your view controllers. I don't understand if your app controls the scanner or receives callbacks from it or what the design is.
     
  3. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #3
    The API requires that THE view controller is declared as such:

    class ViewController: UIViewController,CaptuvoEventsProtocol{

    }

    You then override events in the device and act/update the various parts of the application from there.
     
  4. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    You can have as many view controllers as you like that adopt a given protocol.

    Presumably this protocol has a delegate. You can only have one delegate at a time. But again, you can have as many view controllers as you like that can be the delegate.

    Here's one way you might deal with this: Build a singleton object that adopts the protocol. This object manages the scanner. Starts it, stops it, etc. When events occur the singleton object posts notifications. Your view controllers observe the notifications and update themselves appropriately. Your problem is that the protocol is a one-to-one design while you have multiple view controllers and need a one-to-many design. Notifications are a one-to-many design.
     
  5. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #5
    If I was doing this in one of the 10+ languages I've been doing for 20 or more years - that would be enough. But Swift is not so easy to do simple things like that in. Too much under the hood that I don't have any knowledge of.

    Do you have any hints on how I'd even start that process ?
     
  6. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #6
    There are a lot of blogs and tutorials on singletons in swift. Also, there is a tutorial on iOS Design Patters on the Ray Wenderlich site that describes Singleton among common iOS Design Patterns. Notifications are posted by the NSNotificationCenter and I think you'll find plenty of comments on using that with swift.
     
  7. bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #7
    Notifications are a huge part of iOS. So when the one view is updated have it send a notification and the others are observers and respond when they see the notification.
     
  8. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #8
    The hardware API currently supports single view controller in Swift.
    I just want to update ONE bunch of stuff on the screen ... the size of the screen just means that takes up 3 views.

    Seems ludicrous that I can write this for an iPad in one fashion with 8 lines of code - but to do it for an iPod Touch requires a completely different approach just because the screen is smaller.

    Anyway - we have run out of time.
     
  9. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #9
    Well an iPod screen is a lot smaller than an iPad screen. One way to make the same code work for both is to use a scrollview so the user can get to all the views on the smaller screen. Another simple way would be to use a tableView. It automatically handles some of the details of displaying all the data.
     
  10. bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #10
    This thread seems a bit strange.

    iOS programming is very well documented and what the thread starter is asking for is not difficult to do. As I look at the original there's no mention of the iPod until at the end. Additionally one view controller having multiple views is not difficult to do, they are called "sub views" and are commonly programmed.

    I have an app going to the Store which uses sub views to display more data which is seen by simply tapping on the view contoller's view. The sub view(s) are kept invisible but are updating as the data changes real time. When the user wants to see the data all they have to do is double tap the view, same thing to make it disappear.

    The API is rich and it just takes some imagination and experimenting to find your solution.

    What I think is possibly the thread starter is new to iOS and just needs a bit of practice.
     
  11. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #11
    I am indeed new to SWIFT and XCode, but not new to iOS development.

    Before I can practice I need to know how to do the trivial task set ... that's something that I don't have any idea on sadly, even with what I can only assume have been awesome attempts at help.

    However, at this point in time - my project is dead. I had two weeks to provide a trivial example of use of the device in question, and that is now done. The matter is now back in the hands of my boss.

    The fault here is that Swift imposes arbitrarily strict rules on the otherwise generous MVC pattern.

    It should be trivial for me to do the following (with appropriate abstraction layers added):

    Page1->Label1->Text = Page2->Label2-Text + 'Fred';
     
  12. bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #12
    I assume in the past you have used one of those cross platform code generators for your iOS development and now need to break out of it's limitations.

    I've been developing for iOS about five solid years now and since my previous experience has been in C and forward I use objectiveC for all my code. But Swift is a great language and even though it seems to frustrate you I assure you it has very few limitations. But looking at you brief example it looks like you want to apply some of your past knowledge to Swift.

    Even though Swift is less of an object oriented than objectiveC one has to know inheritance to grasp the iOS API to its fullest.

    It seems like you need your solution now and considering the learning curve for the API that may be difficult.

    I might hint that you can pass references/data from one view controller to another as navigation takes place. A Tab based app builds the basics of the navigation for you and there is a call to get the new view controller and pass it data from the one it is linked to.

    Consider one of the many tutorials on line or maybe Stack overflow.
     
  13. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #13
    Native code compilation via Delphi and native iOS widgets. SO I'm not doing cross platform, targetting iOS specifically.

    And yes - I am trying to get around some serious limitations in Swift, but I'm not convinced they can't be got around yet.

    There is pretty much a single requirement - I have a very tiny bit of programming logic that has to be coded - in a single location. In the trivial example I have given above, I effectively need to be able to do the following (I know the trivial example can be done better other ways - but it is demonstrating my requirements nothing more).

    In some fashion I need to be able to (in the above example) do the the following:

    TabPage1.Label1.Text = TabPage2.Label2.Text + 'Fred';

    That's an unalterable requirement - now of course, I don't expect the syntax to directly edit the labels themselves - but is there a way of using an IBOUTLET from each view to its own view controller - then using some other gizmo to send them to a third view controller which I get to put my programming logic in?

    Sort of an IBOutlet to another IBOutlet or something ?

    Then I could have my logic which is pretty trivial in a single place as required.
     
  14. bjet767, Mar 15, 2017
    Last edited: Mar 15, 2017

    bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #14
    Well what you are doing is using another programing platform for iOS projects and it isn't the "Apple" way of doing things.

    The Apple UITabarController (which has the tabs for each view) contains an array of views associated with the contoller and each view has a "UITabbarItem" (https://developer.apple.com/reference/uikit/uitabbaritem) object which can be modified at any time via the array view controllers associated with the tab bar controller.

    What you are trying to do is use an non-Apple approach.

    In ObjectiveC I would simple use an NSString object (and it can be done in Swift the same way) to match your desire code:

    "TabPage1.Label1.Text = TabPage2.Label2.Text + 'Fred';"

    It might look something like this:

    TabPage1.title = [NSString stringWithFormat@"%@ Fred", TabPage2.title];

    It can also be done is Swift in a similar way.
    TabPage1.title = NSString stringWithFormat "%@ Fred", TabPage2.title

    (Swift version translated via:https://objectivec2swift.com/#/home/main)

    The issue you will have is getting the tabbaritem from the array of view controllers for the tabbar controller.

    Ironically my current App I needed to disable a tab bar item as your question has grown.

    Easy in xCode and OC or Swift; sorry I can't help with Delphi.

    ---------- Looked further at your code

    Your UILabels in each view can only be linked to that view. However, you can modify them as you segue to the new view via your tab view controller.

    Since I can't see you story board I have to assume that when you added the UILabels you also linked them to the public variable in the associated view controller?

    If so the segue has the ability to get the new view controller and public variables prior to the view being opened so you can put some data in them.

    Now here's the problem, if you are using a UITabBarController you will have to call the associated segue method to pass the data.

    I know it's confusing but the basics of the language (objectiveC and Swift) barely touch the depth of the API for iOS. It reminds me of those intro programs "Hello World" which really only make one feel like they can do something when in reality all it does is introduce the text editor, language, API and compiler.

    Enjoy. And truly if one is to become a semi-master (no one is truely a master and if they say they are well then there must be an "alternate truth" out there) of iOS then xCode and OC or Swift is the only way to go.
     
  15. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #15
    It is all moot now, given the 5 minute job couldn't be done within 2 weeks - the powers that be determined that we need to find another path that involves a sensible approach.

    The professional quote we got on the job was 10 hours, so that's the PRO people looking at taking ages for a 5 minute job.

    If that is how long it takes to make the 'demo' - the full version (around 2 day's work) would probably take a life time.
     
  16. AxoNeuron macrumors 65816

    AxoNeuron

    Joined:
    Apr 22, 2012
    Location:
    The Left Coast
    #16
    This is trivial. I don't see how Swift is a limitation here, it seems like you are blaming the language for your own lack of knowledge.

    There's many ways to do this. You could use a global singleton object with a completion block/closure to update both labels whenever required. Just make to keep both view controllers in memory (which they should be since you are using a tab bar VC). You could also use use the singleton to store a string variable, and whenever 'viewDidAppear' gets called on either view controller, just grab the latest value of that property. You could also just use delegation as well. There's a ton of ways to do this....
     
  17. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #17
    Ok - finally got an expert to help me out, it is far from trivial to solve this trivial problem in Swift. The issue is with Swift the language, and not iOS itself as it can be done in 5 minutes in the other language I can develop for iOS.

    Swift is literally incapable of innately allowing business logic to communicate with user interface elements on more than one view, so with various tricks, hacks and work around and the help of the expert and five hours of his time - I have a working platform to move on with.

    The expert has informed me that the approach I'm now using is 'best practice' ... but casting object pointers as integers doesn't seem like a solid underpinning of a best practice to me.

    But - it works.
     
  18. AxoNeuron, May 2, 2017
    Last edited: May 2, 2017

    AxoNeuron macrumors 65816

    AxoNeuron

    Joined:
    Apr 22, 2012
    Location:
    The Left Coast
    #18
    No......this is NOT a limitation with Swift. There are so many ways to allow "business logic to communicate with UI elements on more than one view" that I can't even list all of them.

    You could use NotificationCenter to create a listener in each view which updates the labels whenever the label should change, assuming all views are always kept in memory. You could supplement this with the next suggestion without having to implement the viewDidAppear function.

    You could implement a custom object in your code which has a "latestText" string property, and implement the "viewDidAppear" function in all of your view controllers and update them based on the latestText property whenever the views appear visible to the user.

    Also, you could use a singleton object with a delegate array property so that it can have more than one delegate view.

    Also, depending on what you are doing, you could create a global singleton object, which uses a function that accepts a completion block/closure, and whenever it needs to change label/s it just calls the closure and notifies any subscribed views.

    Keep in mind that with most of these methods you will need to watch out for retain cycles and make sure your references aren't being prematurely released.

    Code:
    class uiListener
    {
        static let shared = uiListener();
    
        var latestText = ""
    
        var listeners : [(_ newText : String) -> Void] = [];
    
        func listenForChanges(_ completion : (_ newText : String) -> Void)
        {
            self.listeners.append(completion);
    
            //now, for whichever views have called this function and implemented the closure, you can call the closure later on and update them all at once.
        }
    
        func changeAllLabels(_ newText : String)
        {
            //store the latest text value
            self.latestText = newText;
    
            // update all views
            for closure in self.listeners {
                closure(newText);
            }
        }
    
    }
    
    In each view, you just need to subscribe to the uiListener function to get label updates in the future, you may want to retain a weak reference to the completion closure. So probably in viewDidLoad call the 'listenForChanges' function:

    Code:
    override func viewDidLoad() {
            super.viewDidLoad()
    
            uiListener.shared.listenForChanges { (newText) in
                self.label = newText;
            };
    }
    
    From now on to change ALL of the labels in your program that are loaded, you just need to call:

    uiListener.shared.changeAllLabels("new label");

    And if a view hasn't yet been loaded, all you need to do is in the viewDidAppear function just call:

    self.label = uiListener.shared.latestText;

    So again, please do not blame your inexperience with the language on the language itself. There are MANY ways to accomplish what you are trying to accomplish.
     
  19. craznar thread starter macrumors regular

    Joined:
    Jun 20, 2009
    Location:
    Brisbane, Queensland, Australia
    #19
    As you go on to describe how it is a limitation of Swift, by writing a trivial APP using one of several convoluted methods.

    I never said it wasn't possible, but for it to be so complex to write a trivial app - that it takes an expert hours to achieve, that is inherently a limitation in Swift.

    That's undeniable. I've written in over 10 languages, and dabbled in another 20 - in all but Swift trivial apps were ... well, trivial.
     

Share This Page