I have 48 view controllers in my storyboard / project. I wish to have 2 different types of Navigation Bar designs.
Style 1 is a navigation bar and a status bar that is white and grey (colours not important to the question)
Style 2 is a navigation bar without a status bar. This is black.
I have set style 1 in the app delegate and set style 2 within one of the views. To a point this works and style 2 overwrites style 1. However, when I navigate away from the view, style 2 continues to override.
I could set each view controller explicitly but with 48 views and 4 or 5 lines of code to define the style is seems inefficient. If I later choose to change the style I then have 48 instances of code to edit.
My major experience is with PHP and if I had this situation I would make an include statement to reference style1 or style2 as needed.
I have tried to create a function in Swift to call the desired design but I cannot get it work as it doesn't reference the UIViewController in the same way you would when adding it directly. I have only been coding for Swift / Xcode for 3 months so it could my lack of knowledge.
I would like to find a solution that on each view I can call something like below (PseudoCode)
override func viewWillAppear(animated: Bool) {
navBarStyle1() or navBarStyle2()
}
I have not included my code to adjust the colours as I feel this is not needed for the answer.
What would be the best way to manage this efficiently? Is there an equivalent to a PHP include? If the solution is a function, could you provide an example? Or may be the solution is something different?
As requested, here is one of my view controllers:
import UIKit
class DeleteMatchViewController: UIViewController {
var idPass = ""
// OUTLETS
#IBOutlet weak var errorMessage: UILabel!
#IBOutlet weak var information: UILabel!
// ACTIONS
#IBAction func deleteMatch(sender: UIBarButtonItem) {
// connect and delete from server
// delete from core data
// load from core data
let urlParameters = "removed for privacy"
let status = sendSeverV2("\(apiUrl)/removedforprivacy.php", parameters: urlParameters)
if status == "OK"
{
// DELETESINGLE firstname|David
myDatabase("Matches", theCommand: "DELETESINGLE", theQuery: "userid|\(idPass)")
myDatabase("Messages", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
myDatabase("Messagesunsent", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
// core data
loadMatchesFromCoreData()
// segue to matches table
performSegueWithIdentifier("jumpMatches", sender: nil)
}
if status == "Error"
{
errorMessage.text = "Connection error"
}
if status == "Security"
{
errorMessage.text = "Authentication error"
authError = "yes"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = false
self.tabBarController?.tabBar.hidden = true
errorMessage.text = ""
information.text = "If you delete this match all messages will be erased and only a future mutual match will all you to contact them again."
}
}
A little OOP can go a long way here.
First, let's make some base view controllers:
class GrayStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
class BlackStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
Now, we can make one of these for every appearance we want, and we can move any shared behavior into these base classes we want. It probably makes sense for your app to have a BaseViewController which the two classes I just posted inherit from (rather than inheriting from UIViewController directly).
Then, all you have to do is make your pile of view controllers inherit from the correct controller based on theme.
class DeleteMatchViewController: GrayStatusBarViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated) // <-- this is super important
// the rest of the view will appear code for this VC
}
}
Alternatively, you can encapsulate the appearance logic in methods in a class like I previously mentioned, the BaseViewContoller, something like this:
class BaseViewController: UIViewController {
func setUpGrayStatusBar() {
// write that logic
}
func setUpBlackStatusBar() {
// write that logic
}
// etc, as many of these as you want
}
And now, you inherit from this class and call the appropriate method:
class DeleteMatchViewController: BaseViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setUpGrayStatusBar()
}
}
When it comes to the run time performance of your application, neither of these solutions is any different from simply copy & pasting the same code into all of your individual view controllers. Just because you didn't paste it in more than one place doesn't mean it doesn't run more than once.
Related
I have created a UIView class (SegmentControl.swift) and added the UISegmentControl using an xib. I have another viewcontroller where I refer this UIView class to add the segment control.
My question here is since I have the UISegmentControl in another UIView class, how will I update the index and load the respective containerViews in the ViewController?
#IBAction func didChangeIndex(_ sender: UISegmentedControl) { } will work only if I have Segment control in ViewController.
Please provide your suggestions on how can I load the containerviews when the UIControlSegment is in another class.
This is the code I have:
SegmentControl.swift
class SegmentControl: UIView {
#IBOutlet weak var segmentView: UISegmentedControl!
// Loading nibs are there.. just that I didn't include here
func create(titles: [String]) {
items.enumerated().forEach { (index, item) in
segmentView.setTitle(item, forSegmentAt: index)
}
}
}
ViewController.swift
func showSegmentControl() {
let segmentedView = GenericSegmentedView.create(items: ["A", "B"])
stackView.addArrangedSubView(segmentedView)
}
Two container views --> aInfoView and bInfoView are intialised in View Controller.
How will I load them on switching the segments since they are in UIView class. I couldnt find any answers here. Please help!
Thank you!!
You can add a target to the segmented control even if it's contained within another view. The segmented control is an accessible property on your SegmentControl class. From the SegmentControl's parent view controller add the target action to the your SegmentControl's child segmentView.
func showSegmentControl() {
let segmentedView = GenericSegmentedView.create(items: ["A", "B"])
stackView.addArrangedSubView(segmentedView)
segmentedView.segmentView.addTarget(self, action: #selector(segmentControlChanged(_:)), for: .valueChanged)
}
// Function will be called when value changed on the SegmentControl's segmentView
#objc func segmentControlChanged(_ sender: UISegmentedControl) {
print(sender.selectedSegmentIndex)
}
Something as aside, interchanging segmentControl and segmentView can be confusing to people not familiar with your code. Maybe a more descriptive name for the SegmentControl like SegmentControlContainer or something would help make the distinction.
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 want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.
All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.
I have been pulling my hair out trying to get this 'Delegate' thing to work in Swift for an App I am working on.
I have two files: CreateEvent.swift and ContactSelection.swift, where the former calls the latter.
CreateEvent's contents are:
class CreateEventViewController: UIViewController, ContactSelectionDelegate {
/...
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
override func viewDidLoad() {
super.viewDidLoad()
/...
contactSelection.delegate = self
}
func updateInvitedUsers() {
println("this finally worked")
}
func inviteButton(sender: AnyObject){
invitedLabel.text = "Invite"
invitedLabel.hidden = false
toContactSelection()
}
/...
func toContactSelection() {
let contactSelection = self.storyboard?.instantiateViewControllerWithIdentifier("ContactSelectionViewController") as ContactSelectionViewController
contactSelection.delegate = self
self.navigationController?.pushViewController(contactSelection, animated: true)
}
ContactSelection's contents are:
protocol ContactSelectionDelegate {
func updateInvitedUsers()
}
class ContactSelectionViewController: UITableViewController {
var delegate: ContactSelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.updateInvitedUsers()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Stuff
self.delegate?.updateInvitedUsers()
}
}
What am I doing wrong? I am still new and don't fully understand this subject but after scouring the Internet I can't seem to find an answer. I use the Back button available in the Navigation Bar to return to my CreateEvent view.
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
This is instantiating a view controller directly, and the value never gets used. Since it looks like you're using storyboards, this isn't a good idea since none of the outlets will be connected and you'll get optional unwrapping crashes. You set the delegate of this view controller but that's irrelevant as it doesn't get used.
It also isn't a good idea because if you do multiple pushes you'll be reusing the same view controller and this will eventually lead to bugs as you'll have leftover state from previous uses which might give you unexpected outcomes. It's better to create a new view controller to push each time.
In your code you're making a brand new contactSelection from the storyboard and pushing it without setting the delegate.
You need to set the delegate on the instance that you're pushing onto the navigation stack.
It's also helpful to pass back a reference in the delegate method which can be used to extract values, rather than relying on a separate reference in the var like you're doing.
So, I'd do the following:
Remove the var contactSelection
Add the delegate before pushing the new contactSelection object
Change the delegate method signature to this:
protocol ContactSelectionDelegate {
func updateInvitedUsers(contactSelection:ContactSelectionViewController)
}
Change your delegate calls to this:
self.delegate?.updateInvitedUsers(self)