A student that completes this project shows that they can:
- use storyboard references to separate UI into multiple storyboards
- use view controller containment and embed segues to embed child view controllers
- use UITabBarController to allow user to switch between multiple view controllers
Poll is an application where a user can submit a vote on a poll of some sort (for example, the question could be "What is your favorite color?"), and see the results of all of those votes.
This application requires you to implement concepts such as using multiple storyboards, container views, tab bar controllers, and custom protocols.
Watch the screen recording below in order to know how final product should look and behave:
The app has three tabs: Voting, Results, and Combined. The Voting tab allows the user to submit a response to the poll. The Results tab displays a table view with submitted results. The Combined tab shows a view controller that uses view controller containment to display both the vote submission interface and the results table view on a single screen.
Please fork and clone this repository.
Create a model object that represents a vote, with the name of the person who voted, and their response.
- Create a new Swift file called "Vote.swift"
- Create a
Vote
class withname
andresponse
properties - Create an initializer for
Vote
. It should takename
andresponse
values.
Create a model controller for Vote
s:
- Make a "VoteController.swift" file and create a
VoteController
class in it. - Add a
votes
property that is of type[Vote]
. Set its initial value to an empty array - Add a "Create" function that should take in a name and response as parameters. The function should initialize a new
Vote
object and append it to thevotes
array.
This application is going to use a UITabBarController
with three items. The first will be a view controller that allows the user to vote, the second is a view controller that shows the results of the votes, and the third will use container views to combine the first two view controllers. In order to practice storyboard references, each view controller will have its own storyboard.
- Create a storyboard called "Voting.storyboard".
- Drag out a
UIViewController
scene, and set it to be the initial view controller of this storyboard. - This scene should have the following views:
- a label that asks a question such as "What is your favorite color?"
- a text field that allows you to enter your name
- a text field that allows you to enter a response
- a button that submits your vote
- Create a Cocoa Touch Subclass of
UIViewController
for this scene calledVotingViewController
and set the storyboard scene's class to it. - Add outlets from your text fields and an action from the button.
- Create a storyboard called "Results.storyboard".
- Drag out a
UITableViewController
scene and set it as the initial view controller of this storyboard - Give the table view cell an identifier and set its style to "Right Detail"
- Create a Cocoa Touch Subclass of
UITableViewController
for this scene calledResultsTableViewController
and set the storyboard scene's class to it.
This view controller will combine the two previously created scenes using container views so they can be viewed at the same time on a single view controller.
- Create a storyboard called "Combined.storyboard"
- Drag out a
UIViewController
scene, and set it to be the initial view controller of this storyboard. - Add two container views to this scene, and have each container view fill about half of the screen.
- Delete the view controller that comes with each container view.
- Add two storyboard references. One should reference the Voting storyboard, and the other should reference the Results storybaord.
- From one container view, add an embed segue to the Voting storyboard reference, and from the other, add an embed segue to the Results storyboard reference. Add identifiers to both segues.
- Create a Cocoa Touch Subclass of
UIViewController
for this scene calledCombinedViewController
and set the storyboard scene's class to it.
In the Main.storyboard, we'll connect all of our storyboards using a tab bar controller so the user can access them.
- Drag out a
UITabBarController
scene and delete the view controller scenes that come with it. - Add three storyboard references. One should reference the Voting storyboard, another the Results storyboard, and another the Combined storyboard.
- From the tab bar controller scene, add relationship segues to each of the storyboard references to add them as items in the tab bar controlller.
- Go back to the Voting, Results, and Combined storyboards. You will notice that the scenes should now have a tab bar item in the Document Outline (the icon is a star). Add an appropriate title in each tab bar item.
- Create a Cocoa Touch Subclass of
UITabBarController
calledPollingTabBarViewController
and set the tab bar controller scene's class to it.
Now we'll add the logic in order for the view controllers to function properly.
Since the tab bar controller is our initial view controller, it will be in charge of initializing an instance of VoteController
. It will pass a reference of it to the three view controllers in the tab bar.
We're going to create a protocol that the view controllers will adopt/conform to in order to make sure that they are prepared to receive that VoteController
reference.
Create a new Swift file called "VoteControllerProtocol.swift". Create a class protocol VoteControllerProtocol
. It should have a single variable called voteController: VoteController?
. It should be gettable and settable.
In the PollingTabBarViewController
create a new instance of VoteController
in a constant called voteController
. This is the instance of the model controller that we need to give access to to any of the tab bar controller's child view controllers that conform to the ViteControllerProtocol
:
- Make a function called
passVoteControllerToChildViewControllers
. It doesn't have any parameters, and it doesn't return anything. - This function will make use of the tab bar controller's
viewControllers
property, which again holds all of its child view controllers that are embedded in the tab bar. This property is optional, so unwrap it using guard-let. - Create a for-in loop that iterates through each view controller in the newly unwrapped
viewControllers
property. - In each iteration of the loop, you should check if the view controller conforms to the
VoteControllerProtocol
(hint: try casting the view controller as the protocol). If it does conform to the protocol, you can pass thevoteController
to the view controller as you will have access to the view controller's ownvoteController
property. - Finally, call this function in the
viewDidLoad()
.
At this point, you haven't made any of your view controllers conform to the VoteControllerProtocol
, but don't worry about that. We'll make them conform to it in the next part.
You should have already created outlets to the text fields and an action from your button. If you haven't, do so now.
Adopt/conform to the VoteControllerProtocol
. This will ensure you have a correctly typed voteController
variable according to the protocol.
The submit button action should call the voteController
's createVote(with: ...)
function. You will need to get the text from the text fields in order to fill in the function's parameters.
In this table view controller, also adopt/conform to the VoteControllerProtocol
.
All you need to do in this view controller is fill in the two required UITableViewDataSource
methods:
- Set the number of rows in the table view equal to the amount of votes in the
voteController
'svotes
property. - Remember that our cell is a "Right Detail" style, so we have two labels. In the
cellForRowAt
method, get the instance ofVote
that corresponds to the cell you are trying to make. (voteController.votes[indexPath.row]
) - Set the
textLabel
anddetailTextLabel
's text to the vote's name and response, respectively. - If it isn't there already, you will need to return the cell at the end of the function.
We will also need to make use of the viewWillAppear
lifecycle method. Call it, and reload the table view inside of it. This will ensure that every time the user goes to this view controller from another tab, the table view will always make new cells for any new votes made.
As this view controller is simply putting the previous two view controllers onto one, there is very little logic required to make this work.
One thing about the container views is that even though they have an embed segue to the Voting and Results view controllers, the view controllers shown in the container views are different instances of those view controllers than the ones in the other two tabs. Because of this, we need to pass the voteController
to these new instances of the view controllers, as they are not the same instances that the tab bar controller will pass the vote controller to.
- Call the
prepare(for segue: ...)
method. - If the segue's identifier matches that of either identifier of the embed segues, then get the destination view controller and cast it as
VoteControllerProtocol
. Remember that this is how the tab bar controller knows if its view controllers have avoteController
property. We're using it here because we again need to pass thevoteController
, so the protocol works fantastic here as well. - If the cast is successful, pass the
voteController
to the cast destination view controller.
As an aside, instead of checking whether the destination view controller conforms to the VoteControllerProtocol, you could simply cast the view controller as the actual subclass that it is (VotingViewController
and ResultsTableViewController
when checking their respective segue identifier) instead of casting them as VoteControllerProtocol
in order to gain access to their voteController
. Both ways are equally valid.
At this point, the application should work. Note that in the CombinedViewController
, when you create a vote, the table view will not reload automatically until you go to another view controller and come back. There is a way to solve this, but we have not talked about it and it is not required to implement it.
If you finish early or want to push yourself, here are some additional goals:
-
Instead of putting the
UITableViewDataSource
andUITableViewDelegate
methods in theResultsTableViewController
, implement a separate, single-responsibility data source class calledResultsModel
orResultsDataSource
. RemoveResultsTableViewController
, and instead use a plainUITableViewController
here. How will you pass data (ie. theVoteController
andResultsModel
) to this table view controller? -
Change your
Vote
model object to have a third property calledtimestamp
. Its type should beDate
. This will allow you to sort the votes in chronological order. Try to implement this. -
Change the table view's prototype cell to be a custom cell. Make a label for each property on the
Vote
model. If you did the previous challenge, make sure that includes showing the timestamp. As a sub-challenge, figure out how to format the timestamp into something that is more human-readable. -
As stated above, the table view in the combined view controller will not reload when a new vote is made from that same view controller without going to a new tab and coming back. Try to get this to work. (hint: There are multiple valid ways to do this. A custom delegate protocol and delegate property would work, as would use of the
NotificationCenter
API.)