I got tab bar controller with three view controllers setup. One of those view controllers changes its tab bar item badgeValue when I open it. I would like to change this badgeValue already when I arrive at the first tab.
I created a Tabbarcontoller: UITabBarController class but don't know how to easily access the items of the sub views. Here is the code from my Tabbarcontoller class:
class TabBarController: UITabBarController, MainMethodsDelegate {
var myFriendsRequests: [UserInfo] = []
var friendRequestsCount: Int = 0
func getFriendsRequests_Methods_Destination(myFriendsRequest: UserInfo) {
myFriendsRequests.append(myFriendsRequest)
self.friendRequestsCount = myFriendsRequests.count
DispatchQueue.main.async {
//friendsBarItem.badgeValue = String(self.friendRequestsCount)
}
}
let mainMethods = MainMethods()
override func viewDidLoad() {
super.viewDidLoad()
mainMethods.delegate = self
mainMethods.getFriendsRequests()
}
}
And here the working code from the sub view controller:
class FriendsViewController: UIViewController, MainMethodsDelegate {
var myFriendsRequests: [UserInfo] = []
var friendRequestsCount: Int = 0
func getFriendsRequests_Methods_Destination(myFriendsRequest: UserInfo) {
myFriendsRequests.append(myFriendsRequest)
self.friendRequestsCount = myFriendsRequests.count
DispatchQueue.main.async {
self.friendsBarItem.badgeValue = String(self.friendRequestsCount)
}
}
let mainMethods = MainMethods()
override func viewDidLoad() {
super.viewDidLoad()
mainMethods.delegate = self
mainMethods.getFriendsRequests()
}
#IBOutlet weak var friendsBarItem: UITabBarItem!
}
I'm looking for a simple way to access the sub view controllers or at least the bar items where it says:
//friendsBarItem.badgeValue = String(self.friendRequestsCount)
I'm not sure if delegates is the right way to go?
I would leave the bar item out of the friends controller. It does not need to know about the tabBar. If I were you, I would create a protocol to let know your tabBarController that the number of friend requests has changed.
First, define the protocol:
protocol FriendsRequestDelegate {
func friendsRequestsDidChange(number: Int)
}
Then, add the variable to your FriendsViewController:
weak var delegate: FriendsRequestDelegate?
And, we need to trigger that func, still in FriendsViewController, after fetching the number of friends request, add;
delegate?.friendsRequestsDidChange(number: myFriendsRequests.count)
Finally, make your TabBarController conform to this protocol;
extension TabBarController: FriendsRequestDelegate {
func friendsRequestsDidChange(number: Int) {
friendsBarItem.badgeValue = String(number)
}
}
See what I mean? This way your friendsRequestController doesn't know about the tabBar and it'll keep your code clean.
I've found a quite simple way to access the barItems. I only had to access the array of tabBar items with:
tabBar.items![2].badgeValue = ""
Here is my code for the UITabBarController:
class TabBarController: UITabBarController, MainMethodsDelegate {
var myFriendsRequests: [UserInfo] = []
var friendRequestsCount: Int = 0
func getFriendsRequests_Methods_Destination(myFriendsRequest: UserInfo) {
myFriendsRequests.append(myFriendsRequest)
self.friendRequestsCount = myFriendsRequests.count
DispatchQueue.main.async {
self.tabBar.items![2].badgeValue = String(self.friendRequestsCount)
}
}
let mainMethods = MainMethods()
override func viewDidLoad() {
super.viewDidLoad()
mainMethods.delegate = self
mainMethods.getFriendsRequests()
}
}
The sub view controller does not need to check and update the badgeValue anymore. (This is fine in my case since the getFriendsRequests_Methods_Destination(myFriendsRequest: UserInfo) method gets triggered as soon as there is a new friends request anyways using a (Firestore) snapshot listener.)
My initial way was to access the tabItem from each of the sub view controllers individually using tabBarController!.tabBar.items![2].badgeValue = "" But this would have been redundant in my case.
Related
Good night!
Can you tell me how can I write data from controller 2 to controller 1?
I have a coordinate at the main screen.
final class MenuCoffeLikeCoordinator: TabBarPresentableCoordinator {
var tabBarItem: UITabBarItem = {
let title = "Меню"
let image = UIImage(asset: Resources.Assets.TabBarItems.mainTabBar)
let selectedImage = UIImage(asset: Resources.Assets.TabBarItems.mainTabBarSelected)
let item = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
return item
}()
var navigationController: UINavigationController
init(navigationController: UINavigationController = UINavigationController()) {
self.navigationController = navigationController
}
var didFinish: (() -> Void)?
func start() {
self.navigationController.pushViewController(createMenuCoffeLikeFlow(), animated: true)
}
func stop() {}
func createMenuCoffeLikeFlow() { -> UIViewController {
let menuController = MenuCoffeLikeAssembler.createModule()
menuController.rx.didTapMapLayer.onNext {
let controller = self.createCoffeeBarMap()
self.navigationController.pushViewController(controller, animated: true)
}
return menuController
}
private func createCoffeeBarMap() -> UIViewController {
let controller = CoffeeBarContainerAssembler.createModule()
controller.obsRelay.subscribe(onNext: { event in
self.navigationController.popViewController(animated: true)
})
return controller
}
}
In the createMenuCoffeLikeFlow function, I create the main screen, and when I click on the button, I go to screen 2 (createCoffeeBarMap)
Inside the function (createCoffeeBarMap), I subscribe to the PublishSubject, and when the data changes, I get a new text.
I need to write this text in the menuCoffeeControler which is in the createMenuCoffeLikeFlow function. How can i do this?
Here's how I would implement it using my Cause Logic Effect (CLE) architecture. With CLE you don't need to implement a Coordinator because a reusable Coordinator class already exists in the library. This means less code for you to write.
Unlike yours, this sample is complete and will compile. The only thing missing is the creation and layout of the views inside the view controllers.
import Cause_Logic_Effect
import RxCocoa
import RxSwift
import UIKit
/// This function produces the view controller that is attached to your tab bar controller. I don't put the
/// `UITabBarItem` in here. Instead I attach that when connecting to the tab bar controller.
func menuCoffeLikeTab() -> UIViewController {
// the `configure` function calls its closure inside the viewDidLoad method.
let menuController = MenuController().configure { $0.connect() }
let controller = UINavigationController(rootViewController: menuController)
return controller
}
/// It depends on how you want to layout your view controllers on whether anything else goes in here. If you
/// use storyboards, then add `#IBOutlet` before the views here. If you create your views programatically
/// then add a `loadView()` override.
final class MenuController: UIViewController {
var mapLayerButton: UIButton!
var textField: UITextField!
let disposeBag = DisposeBag()
}
extension MenuController {
func connect() {
// This is the meat. The `coffeeBarResponse` observable pushes the
// CoffeeBarController onto the navigation stack when approprate and
// then emits any values produced by it. Notice how this looks alot like
// a network call except you are querying the user instead of the server.
let coffeeBarResponse = mapLayerButton.rx.tap
.flatMapFirst(pushScene(on: navigationController!, animated: true) {
CoffeeBarController().scene { $0.connect() }
})
.share()
// The pushScene function above will create a coordinator for the
// CoffeeBarController. When needed, the coordinator will create the
// view controller, call its `connect` and emit any values from that.
// When the Observable completes, the coordinator will pop the view
// controller off.
coffeeBarResponse
.bind(to: textField.rx.text)
.disposed(by: disposeBag)
}
}
final class CoffeeBarController: UIViewController {
var saveButton: UIButton!
var textField: UITextField!
}
extension CoffeeBarController {
func connect() -> Observable<String> {
// when the user taps the save button, this will emit whatever value is
// in the text field and then complete the observable.
saveButton.rx.tap
.withLatestFrom(textField.rx.text.orEmpty)
.take(1)
}
}
Like I said above, this uses a reusable Coordinator class that is part of the library instead of you having to write your own all the time. This architecture will significantly reduce the amount of boilerplate code you have to write. Learn more at https://github.com/danielt1263/CLE-Architecture-Tools and join the RxSwift Slack to learn more about RxSwift in general.
this is a typical scenario where DI comes to rescue. You have to have some kind of a shared container which will register and resolve dependencies. I use Dip https://github.com/AliSoftware/Dip.git and here is an example with your code. The idea is the following - you register closure in one VC and pass it to another.
I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.
im developing an app that utilises many buttons( possibly 20 buttons) on one primary view controller that can are all able to activate a singular picker view within a pop up on a seperate view controller. i don’t think the answer is lots and lots segues. Is there a better approach I should be considering?
I’m thinking - some kind of multiuse segue that can be activated by any of the buttons, but nonidea how this is done.
Appreciate any advice
Mike
Set up all buttons to same action such as:
#IBAction func keyPressed(_ sender:UIButton){
// use button title string
self.keyString = sender.titleLabel?.text as! String
// or tag
self.keyTag= sender.tag?
self.performSegue(withIdentifier: "TheSegue", sender: self)
}
Then you would want to set up the View Controller that you are going to navigate to based on the state of the sender. So you would override the prepare:forSegue method as below.
override func prepare(for segue:UIStoryboardSegue, sender:Any?) {
let destController = segue.destination as! Dest_Controller_Class
// use tag or keyTitle to set controller attributes
// before view is shown
destController.keyTag = self.keyTag
destController.keyString = self.keyString
}
Now once you've navigated to the Dest_Controller_Class, you will have the properties of the button pressed locally in the view controller and could update the view as you see fit:
class Dest_Controller_Class: UIViewController {
var keyString: String?
var keyTag: Int?
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
if (keyString != nil) {
label.text = keyString;
// or likewise use tag
} else {
label.text = "keyString not set"
}
}
}
I have a game where I store the value of the high score of a player as "highScore", in the first view controllers.
What I want to do is to try to transfer this variable data, to the second view controller.
I entered in the following code in the first view controller's storyboard.
Btw, highscore2, is the variable that I delcared in the second view controller, to store the data from the first highscore variable.:
override func prepareForSegue(segue: UIStoryboardSegue!, sender:AnyObject!)
{
if (segue.identifier == "segueTest")
{
var svc = segue!.destinationViewController as! viewTwo;
svc.highScore2 = "High Score \(highScore)"
}
}
Now here is the code in my second view controller (viewTwo):
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
var highScore2:String!
override func viewDidLoad() {
highScoretwolbl.text = highScore2
}
}
For some reason, the code compiles, but the high score is not displayed, and is "nil".
Avoid forced unwrapping.
if let svc = segue.destinationViewController as? viewTwo{
svc.highScore2 = "High Score \(highScore)"
}
Then put in a breakpoint and make sure you got to all of these lines.
As stated in other answers, viewDidLoad only gets fired once. Since you
are using a forced unwrapped string, it is initially nil. It (highScore2) won't get a value until you set it which happens after viewDidLoad. Add some print statements so you can see the sequence of events.
Your best option is to use viewWillAppear. This will get fired every time
your view appears. This is what you want.
You don't need to use a force unwrapped variable. Just make a string a
make it empty initially.
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?)
{
if segue.identifier == "segueTest"
{
if let svc = segue.destinationViewController as? viewTwo {
print("setting score in prepareForSegue")
svc.highScore2 = "High Score \(highScore)"
}
else {
print("something bad happened")
}
}
}
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
// The following uses forced upwrapping which means it could be nil
// if you never set it and your app will crash.
// var highScore2: String!
// Instead, do this:
var highScore2: String = ""
override func viewWillAppear(animated: Bool) {
// putting this here will ensure that every time the screen
// is shown, the label will get set from the string.
highScoretwolbl.text = highScore2
}
}
If I recall correctly, the viewDidLoad method is called only once when the view controller is first initialized. So you are setting the highScoretwolbl variable before highScore2 actually has any data, this is why it is nil.
I would recommend setting the text inside viewWillAppear so it updates every time the segue happens.
So something like
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
highScoretwolbl.text = highScore2
}
Here is a reference to the lifecycle of all the methods of a view controller. View controller lifecycle
All,
I have set up a protocol, and the view controller is the delegate of this protocol, like so :
import Foundation
protocol PayButtonProtocol {
func enablePayButton()
func disablePayButton()
}
And the view controller is the delegate :
class ViewController: UIViewController, PayButtonProtocol
The protocol functions are as follows :
func enablePayButton() {
println("Button enabled")
PAYBarButton.enabled = false
}
func disablePayButton() {
PAYBarButton.enabled = false
}
I set a class and assign the delegate :
class Trigger
{
var delegate:PayButtonProtocol?
func EnablePayButton()
{
delegate?.enablePayButton()
}
}
Then I set the trigger to run the function :
let localtrigger = Trigger()
localtrigger.delegate = ViewController()
localtrigger.EnablePayButton()
This works and the 'button enabled' is displayed in the console. But the Bar Button (PAYBarButton) is nil and it seems that the view controller has lost its hieracy as I cannot access any of the view controllers objects. The View Controller was built with interface builder. Anyone got any ideas ? Is it
localtrigger.delegate = ViewController()
that rebuilds the viewconotroller and makes the original one not accessible ? If so how do i do this ?
if you are creating the localTrigger object inside your ViewController class you can just do:
let localtrigger = Trigger()
localtrigger.delegate = self // self is an instance of ViewController
localtrigger.EnablePayButton()