In the struggle of developing a Tabbed IOS App with Swift 1.2 and Xcode 6.3 based on MVC, I'm using the visual Storyboard elements instead of to do it programatically because I'm not an experienced developer. In the image attached below you can see the Architecture in the StoryBoard of the App:
The App consists in:
One TabBarController with four TabBar Items.
Each Item has its own ViewController in Storyboard.
All of them are linked with the relationship seque(ViewControllers) in StoryBoard.
Each ViewController in the StoryBoard has its own Class.
The last Item has an embedded NavigationController because I'm using a PageMenu project https://github.com/uacaps/PageMenu to include a paging menu controller with a two child ViewControllers
The Issues I'm having until this point are:
The two child ViewControllers are not linked with the Last TabBar Item in the StoryBoard,as you can see in the figure above, only are instantiated in the parent ViewController Class(PageMenuViewController1), normally this PageMenu works but sometimes the last TabBar Item dissapears, I'm very confused with this issue.
The override func viewWillAppear into the default child ViewController is called twice at the first time, I've include a println("ClubsController viewWillAppear").
The code of the ViewControllers is
import UIKit
class ClubsViewController: UIViewController, UITableViewDataSource{
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var clubs: [Club]!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("ClubsController viewWillAppear")
apiClient.clubsService.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview?.reloadData()
}
else {
println("error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clubs?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("clubObjectCell") as! ClubTableViewCell
cell.clubObject = self.clubs?[indexPath.row]
return cell
}
}
The code of the PageMenuViewController is
import UIKit
class PageMenuViewController1: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidAppear(animated: Bool) {
println("PageMenuViewController1 viewWillAppear")
super.viewDidAppear(animated)
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller1 = storyboard!.instantiateViewControllerWithIdentifier("ClubsViewController")! as! ClubsViewController
controller1.title = "CLUBS"
controllerArray.append(controller1)
var controller2 = storyboard!.instantiateViewControllerWithIdentifier("PartiesViewController")! as! PartiesViewController
controller2.title = "PARTIES"
controllerArray.append(controller2)
// Customize page menu to your liking (optional) or use default settings by sending nil for 'options' in the init
// Example:
var parameters: [CAPSPageMenuOption] = [
.MenuItemSeparatorWidth(4.3),
.UseMenuLikeSegmentedControl(true),
.MenuItemSeparatorPercentageHeight(0.1)
]
// Initialize page menu with controller array, frame, and optional parameters
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}
}
Appreciate help to accomplish the best practices until this point.
I've not familiar with the CAPSPageMenu but there is nothing wrong with having scenes in a storyboard that aren't connected with a segue - this is just a convenience to help with transitions, and instantiating them with instantiateViewControllerWithIdentifier is totally legitimate.
Something that does stand out looking at your storyboard is the way your table view controller with the navigation view controller is wired up.
The navigation viewcontroller should have the relationship with the tab bar controller - not the table viewcontroller.
Here's a screenshot of how the connection should look. Possibly this is why you're sometimes loosing a tab.
Related
I am trying to create a navigation for my iPhone "Tabbed App" which would consist of (obviously) UITabBarController and SWRevealViewController for revealing side-menus.
All the views in my application must have both UITabBarController and UINavigationBar displayed, however, links which appear in left-side menu (handled by SWRevealViewController) must not appear in UITabBarController.
My left-side menu links are handled in this way:
import UIKit
class MenuTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndex = (indexPath as NSIndexPath).row + 1 // hardcoded for time being
let tabBarController = revealViewController().frontViewController as! UITabBarController
let navController = tabBarController.viewControllers![selectedIndex] as! UINavigationController
navController.popToRootViewController(animated: true)
tabBarController.selectedIndex = selectedIndex
revealViewController().pushFrontViewController(tabBarController, animated: false)
}
}
Now, I tried to remove a link for one of the views which I don't want to show in my UITabBarController as follows:
import UIKit
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let index = 2 // hardcoded for time being
viewControllers?.remove(at: index)
}
}
but if I click associated link in left-side menu now, I get an NSRangeException index 2 beyond bounds [0 .. 1] error (of course, because I removed the particular tabBarItem from UITabBarController).
My question is: how can I "hide" the item from UITabBarController but still being able to reference it (and open it) from my side menu?
UPDATE
My storyboard at the moment looks like this:
It's probably not a good idea to use a "menu" to manipulate the Tabs - that's what Apple has designed the More... and Edit... features for.
Depending on your overall design / navigation / user experience flow, two reasonable options would be:
Instead of replacing the current selected Tab, .present a modal view controller, with a "Cancel" or "Save" or "Done" button to .dismiss it (whatever would be appropriate).
Since you state each Tab's ViewController is a NavigationController, you could .push the menu-selected view controller onto the current stack. Then your interface could use the standard "< Back" button navigation.
Good Luck :)
I'm using XLPagerTabStrip to switch among a collection of view controllers. I have three view controllers and I would like that middle view controller is shown by default as first.
I could use
let parentViewController = self.parent! as! ParentViewController
parentViewController.moveToViewControllerAtIndex(1)
inside my first view controller, but that first view controller loads some data from the server and if I switch to another view controller while it is loading data, that first view controller will freeze and it won't load data.
Is there a way to show middle view controller as first by default?
jump to the defenition of 'currentIndex' and change it to public from private. then you can select your current controller by this code:
currentIndex = 1
In function:
override func viewControllers(for pagerTabStripController:
PagerTabStripViewController) -> [UIViewController] {
// This line will help you achieve the requirement
pagerTabStripController.currentIndex = /* required index */
}
It will work smoothly after you make currentIndex in PagerTabStripViewController as public.
To prevent loading the first tab, moveToViewControllerAtIndex() must be called before viewDidLoad() is called in your PagerTabStripViewController subclass.
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
pagerTabStripController.moveToViewController(at: 0) // required index
}
For Move Specific Tab XLPagerTabStrip in swift 5
override func viewDidAppear(_ animated: Bool) {
if nowFrom == "sendvc"
{
self.moveToViewController(at: 3,animated: false)
}
}
You have to use the following lines:
override func viewDidAppear(_ animated: Bool) {
self.moveToViewController(at: 2)
reloadPagerTabStripView()
}
EDIT
I think my problem is that I add the views as subviews in the same view, thats why I can't remove it ?
Im trying to learn swiping between views using XIB.
My storyboard contains 3 views
-Login
-Create Account
-View with scrollview that scrolls between a tableview and a blank view. This view has an embedded navigation controller (Editor -> Embed In -> Navigation Controller)
I Don't want the navigation controller to be shown in my blank page.
I have created the tableView Controller and the blank UIControllerView by adding them as "addChildViewController", See code below
import UIKit
class MasterViewForScroll: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
let Inbox : FriendlistTableBarView = FriendlistTableBarView(nibName: "FriendlistTableBarView", bundle: nil)
let Camera : CameraViewController = CameraViewController(nibName: "CameraViewController", bundle: nil)
func creatingSubViews() {
self.addChildViewController(Inbox)
self.scrollView.addSubview(Inbox.view)
Inbox.didMoveToParentViewController(self)
Inbox.navigationController?.navigationBar.hidden = false
var CameraView = Camera.view.frame
CameraView.origin.x = self.view.frame.width
Camera.view.frame = CameraView
self.addChildViewController(Camera)
self.scrollView.addSubview(Camera.view)
Camera.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.width * 2, self.view.frame.height)
}
override func viewDidLoad() {
super.viewDidLoad()
creatingSubViews()
}
So my question is: How do I hide the navigation controller in the "Camera" view.
Thank you
In your CameraViewController class, add the following code:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBarHidden = true
}
Next, in your FriendlistTableBarView class (I guess it is a UIViewController subclass), add the following code:
override func viewWillAppear() {
super.viewWillAppear()
self.navigationController?.navigationBarHidden = false
}
So, when you swipe to the right - navigation bar will hide, and when you swipe to the left - it will appear again.
Below is a screenshot of the storyboard for my project:
As you can see, I have a UIViewController with a UITableView embedded in a UINavigationController. When the user adds an item to the UITableView a new Realm Object model which I have named Project is created (I'm using RealmSwift) and they are taken to the first item in the UITabBarController. I'm wondering if it is possible to associate the UITabBarController and all of its child view controllers with the Project that is created.
Here is the code that creates the Object (which is working fine):
func add(withName projectName: String) {
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) {
autoreleasepool {
let realm = try! Realm()
let newProject = Project(value: ["name": projectName, "date": NSDate()])
realm.write({ () -> Void in
realm.add(newProject)
})
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
selectedProject = array[indexPath.row]
print(selectedProject)
self.performSegueWithIdentifier("showTab", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTab" {
let dvc = segue.destinationViewController as! TabBarViewController
dvc.project = selectedProject
}
}
As you can see, I'm passing the new Project to the UITabBarController.
Here is my code in the UITabBarController:
import UIKit
import RealmSwift
class MasterTabViewController: UITabBarController {
var project: Project?
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - UI Styling
private extension MasterTabViewController {
func configureNavigationBar() {
navigationItem.title = project?.name
}
}
This sets the title of all UIViewControllers that descend from the UITabBarController.
Is there a way for me to make that Project object available to all view controllers that descend from the UITabBarController? I haven't been able to find too much information on this. One suggestion that I found was to store the Object in NSUserDefaults but it seems to me that there must be a better way.
EDIT: I forgot to add that I can think of a way to do this with Core Data using NSManagedObjectContext, but I'm having some trouble seeing the equivalent in Realm.
You were on the right track! When a view controller is a child of a UITabBarController, you can access the tab bar controller from inside the child view controller with the self.tabBarController property.
Since you know these view controllers are children of a subclass of UITabBarController that also provides Project as a property, it should be a matter of simply calling that tab bar controller property, typecasting the resulting view controller to your tab bar controller subclass, and then calling the project property on it.
So, in any of your child controllers:
let project = (self.tabBarController as MasterTabViewController).project?
You could easily optimize this further by making project a read-only property of your child controller classes that calls this line of code automatically. Good luck!
I am currently trying to pass data between two UINavigationControllers with a UITableViewController attached to each. I am navigating between these two controllers via a UITabBarController. I have been trying to use vacawama's solution on Changing VC issue in Swift. How to pass data between views in tab bar controller? I am using the following code.
import UIKit
class placeData: Equatable {
var description : String
var selected : Bool
init (description : String, selected : Bool) {
self.description = description
self.selected = selected
}
}
class PlacesTabBarController: UITabBarController {
var placeDataArray = [placeData]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is in the tabBarController custom class. In the other two controllers I declare the array I want to be pass between the view controllers and use the method in the link to populate the UITableViewController
var placeDataArray = [placeData]()
override func viewWillAppear(animated: Bool) {
placeDataArray = (self.tabBarController as PlacesTabBarController).placeDataArray
}
When the controllers load, however, the arrays are empty. In the example in the link, all the rest of the code is within the viewWillAppear function, where I need my array to be available to all of the corresponding tableView functions. I am not sure if I am just equating the arrays to zero on load. But my thought was that they would repopulate. Not sure what the correct way to go about this is. Any help would be great.
EDIT: My current structure is as follows:
UITabBarController
| |
UINavigation UINavigation
Controller Controller
| |
UITableView UITableView
Controller Controller
The array is populated when the first tab loads. What I am trying to do is have the populated array in the second view controller, and if I edit it in the second view controller, I want the edits to stay in the first. So I want it to be passed by reference.
I would subclass the UITabBarController and make it a delegate for the two UITableViewControllers.
CustomTabBarController
protocol CustomTabBarDelegate {
var places:Array<placeData> { get set }
}
class CustomTabBarController: UITabBarController, CustomTabBarDelegate {
var places = Array<placeData>()
override func viewDidLoad() {
places = [PlaceData(),PlaceData()]
var table1 = CustomTableViewController()
var table2 = CustomTableViewController()
table1.delegate = self
table2.delegate = self
var navController1 = UINavigationController(rootViewController: table1)
var navController2 = UINavigationController(rootViewController: table2)
self.viewControllers = [navController1, navController2]
}
}
Then your TableViewControllers simply access the delegates array like so.
CustomTableViewController
class CustomTableViewController: UITableViewController {
var delegate:CustomTabBarDelegate!
override func viewDidAppear() {
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return delegate.places.count
}
}
Any changes to the array will be visible in each table after you .reloadData() - I have set the CustomTableViewController in my example to reload data every time the view appears, so when you change tabs they should refresh to show the latest changes.
It's worth mentioning that in time it would probably be cleaner to have a separate class that manages your data instead of holding the array in the TabBarController.
You can always use the app delegate..
Set a property there and call it from anywhere in your application.
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var yourNeededData = appDel.yourPassingAroundDataProperty