Split view in container view loses function of navigation controller - ios

i'm building an app for which i need a split view controller. I embedded it in a container view so i could use a segue to acces it. The problem now is that I can't use my navigation bar in a proper way anymore. Normally, there should be a button to go to the previous page, but now I only have the option to go to the homepage (for within the split view controller). You can find some images here. Also, the title will always be "Shoppen", even though I stated it otherwise.
I suppose the problem is that the navigation bar of the view controller in which the container view is set will always be the top one. I have no idea on how to either delete or fix this problem though, could anybody here help me with this? Thanks in advance!
Some code to show you what it should normally do:
In the first section, the table view, it should indeed always title "Shoppen" and able to go back to the homepage
In the detail section, we should be able to go to the table view page, and the title should be the category of the shop we clicked. This is defined here:
func configureView() {
if let detailWinkel = detailWinkel {
if let detailDescriptionLabel = detailDescriptionLabel, WinkelImageView = WinkelImageView {
if detailWinkel.name == "Hunkemoller" {
detailDescriptionLabel.text = "Hunkemöller"
} else {
detailDescriptionLabel.text = detailWinkel.name
}
WinkelImageView.image = UIImage(named: detailWinkel.name)
title = detailWinkel.category
StraatDescriptionLabel.text = detailWinkel.straat
AdresDescriptionLabel.text = detailWinkel.adres
WebsiteButton.setTitle("Open website in Safari", forState: UIControlState.Normal)
}
}
}

I don't completely understand what you are trying to do.
An embed segue is not a normal segue. You can't push a view that is embedded into another view controller onto a navigation stack, if that's what you are saying.
An embed segue is a degenerate case. It invokes the contained view controller at the time the parent view controller is loaded, and there is no mechanism for going back.

Related

UI testing a tab bar controller

I have built a simple tab bar with 3 tabs.
I want to run a UI test to make sure that if the user clicks a tab bar item, the correct view controller shows. How would I go about doing that? Below is the code I would start with, just don't know how to write my assertion.
func testTabBarMyProfileButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["My Profile"].tap()
}
func testTabBarGraphsButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["Graphs"].tap()
}
func testTabBarAboutButton() {
let tabBarsQuery = XCUIApplication().tabBars
tabBarsQuery.buttons["About"].tap()
}
You can access the tabbar button by its position:
app.tabBars.buttons.element(boundBy: 2).tap()
If you have different controls in each view controller shown on each tab bar, you can make assertions if they exist or not (what is expected).
For example if the first tab bar has UILabel named "First name" you can assert if it exists by writing
Let theLabel = app.staticTexts["myValue"]
XCTAssert(theLabel.exists).to(beTrue)
And on the other screens do the same thing for the different controls.
If anyone finds this looking to UI test the contents of another app, I just found a solution..
The tab bar item is a lazy variable and needs to be touched before you can reference a tab bar button by value. Add this line:
tabBarItem.accessibilityIdentifier = "my-snazzy-identifier"
to the viewDidLoad method and you should be able to do this in your UI tests:
app.tabBars.buttons["Button Title"].tap()
You can test the title of the navigation bar.
XCTAssert(app.navigationBars["Graphs"].exists)
See my GitHub repo for a more detailed UI Testing example.

iOS - How to use a small view in different view controllers in Swift

I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)

Change view controller maintaining navigation hierarchy

Using the diagram below as a reference, I'm trying to go from controller A to controller B-2 when the users pushes a button.
I'm fairly new to iOS so I'm having some problems.
If I push the B-2 controller or create a segue the hierarchy is not respected and the B-2 controller is not inside the navigation controller (B).
I want the user to still have the navigation funcionalities and to still be able to go back to controller B-1 using the back button.
Controllers A & B are both under the same Tab controller (Z)
How can I achieve this?
In the end I found this solution:
From controller A I get the parent (Z), set the active controller to the second child (B) and get his child (B-1).
Once I have B-1 I can call perform segue and it works pretty good.
The hierarchy is correct and it's exactly the behaviour I wanted
The code is this
if let tab = self.parentViewController as? UITabBarController{
tab.selectedIndex = 1
if let one = tab.childViewControllers[1] as? UINavigationController{
if let two = one.childViewControllers[0] as? MatchTableViewController{
two.performSegueWithIdentifier("open_chat_view", sender: matchModel)
}
}
}

Reload tabBarController after switching language

In the settings section of my app the user has the option to change the language of the app. So when the user chooses spanish as his primary language the app show the content in spanish after he did an app restart but I want to change the language on the fly. This works for the main content like a TableView because I simply can reload the data but the language in my TabBarController does not change because I don't know how.
So I want to update (or better call it a reset) the TabBarController. After the reset it should display all navigation points in the new language.
My idea was to remove the current TabBarController and initialize a new one. Is this possible? Or is there a better way?
I am not an native english speaker so if my explanations aren't clear enough, just tell me and I'll try to rephrase them.
It might look scary and complicated because of my long post, But it really isn't, it is just long because I thought it would be better to also explain how to do it, instead of just giving a few lines of code.
You can achieve what you want using UITabBarController properties.
UITabBarController have a property called tabBar, which is the actual UITabBar.
One might think that in order to achieve what you want, you should edit this property,
HOWEVER, editing this property would cause an exception.
From apple's UITabBarController documentations, regarding the tabBar property:
You should never attempt to manipulate the UITabBar object itself stored in
this property. If you attempt to do so, the tab bar view throws an exception.
So you should never attempt to edit this property at runtime.
After that word of warning, here is what you should do-
UITabBarController also have a property called viewControllers, which is an NSArray who holds reference to the view controllers that being displayed by the tab bar.
This property CAN be modified at runtime, and changes applied to it are updated instantly in the tab bar.
However, for your case, you don't need to modify this property,
But I thought you should know that so if in some situation you will need to add or remove some items from your tab bar, you'll know that can do it.
What you do want to do, is iterate through the objects of that array to access the view controllers themselves.
UIViewController have a property called tabBarItem which represents the UITabBarItem of the view controller.
So what we are basically doing, is getting the tab bar item of the view controller, but instead of getting it from the UITabBarController itself, we are getting it directly from each view controller.
Each UITabBarItem has a title property, and this is what you want to change.
So now, after that long introduction, let's get to the actual code.
I think a pretty easy way to achieve what you want is to iterate thru the viewControllers array, and have some switch statement in there that would change the title.
As in any programming situations, this can be done in countless other ways, so you might have a better way to implement it than my example below, but this should do the trick.
Each view controller that being displayed in a tab bar controller, have a reference to that tab bar using the property tabBarController
So you can run this code in any of the view controllers that being displayed in the tab bar, and simply use self.reference to get a reference to it.
Add this somewhere after the language have changed-
for (int i = 0; i < [self.tabBarController.viewControllers count]; i++) {
if([self.tabBarController.viewControllers[i] isKindOfClass: [UIViewController class]]) {
UIViewController *vc = self.tabBarController.viewControllers[i];
switch(i) {
case 0:
vc.tabBarItem.title = #"primero";
break;
case 1:
vc.tabBarItem.title = #"secondo";
break;
}
}
}
What we are basically doing, is running a for loop that iterating thru all of the items in the array,
The items in the array are in the same order that they appear on the tab bar,
then we use a switch statement to change the title for the view controller in the corresponding position,
Since array have index 0, the first view controller is at position i=0 and the last one is at one less than the count of items in the array.
Some might argue that my if is unnecessary,
Since we already know that this array holds only view controllers, there is no need to check if the item at that position is of UIViewController class, or a subclass of it.
They might be right, but I always say it's better to be safe than sorry.
Of Curse I would also include in your code something to actually check to what language the user have chosen.
The example above changes the titles to spanish, regardless of the user's choice.
Hope it helps mate,
Good luck
#AMI289 gave a good idea.
Make an extension for UITabBarController and do a loop there. Can call anywhere from the tabBarControllers stack.
In my case after the tabBarController goes navigationController.
// MARK: - UITabBarController
extension UITabBarController {
func changeTitleLocale() {
guard let viewContollers = self.viewControllers else { return }
for (index, navVC) in viewContollers.enumerated() {
if let view = navVC as? UINavigationController {
if let topView = view.topViewController {
if topView.isKind(of: ProfileVC.self) {
self.tabBar.items?[index].title = "tab_profile"
} else if topView.isKind(of: ChatVC.self) {
self.tabBar.items?[index].title = "tab_chat"
} else if topView.isKind(of: PicturesVC.self) {
self.tabBar.items?[index].title = "tab_pictures"
} else if topView.isKind(of: VideosVC.self) {
self.tabBar.items?[index].title = "tab_videos"
}
}
}
}
}
}
Then, when we change a language just run it:
self.tabBarController?.changeTitleLocale()

Detecting that back was pressed to get to current view controller

I am using the navigation controller to go back from one view to previous view using the code below.
ChildViewController.swift:
self.navigationController.popViewControllerAnimated(true)
I need a way to detect that the navigation controller went to the previous view in the actual previous view like below.
ParentViewController.swift:
func backWasPressed(viewControllerIdentifier: String!) {
// if back was pressed from this view controller and not from any other view
if viewControllerIdentifier == "ChildViewController" {
// do stuff here
}
}
Is there anyway to do this?
Take a look at UINavigationControllerDelegate
You don't need to know this. You may think you do, but you don't. This entire proposed architecture is specious:
func backWasPressed(viewControllerIdentifier: String!) {
// if back was pressed from this view controller and not from any other view
if viewControllerIdentifier == "ChildViewController" {
// do stuff here
}
}
If a pushed view controller has some info to communicate to a view controller further down the stack, that is the job of the pushed view controller when it is popped. It knows it is being popped, and it knows how to access the other view controller (and you can use a delegate architecture if there's any doubt about that), so the problem is properly solved in that way. It's exactly the same as when a presented view controller needs to communicate back to its presenter at dismissal time.

Resources