Passing data between views in ONE ViewController in Swift - ios

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.

Related

Load segmented views in ViewController but UISegmentControl is in UIView class

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.

Delegating action through protocol not working swift

I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.

Update subviews of UIView from ViewController

I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.

ViewController or UIView should be in control?

So I have a 'ListViewController' that has a subview called 'ListView'
ListView is the master view that holds all remaining subviews.
Inside listView, there is a subview called PhotoView and inside it, it has a UIButton that toggles the constraints of PhotoView, resizing based on arbitrary values.
Hierarchy is as follows:
ListViewController <- ListView <- PhotoView
The reason I've done this is to minimize the amount of code inside each view, to segment it out.
My question is: Who should be in charge of the 'resizing' function? My
understanding is that a viewController should generally handle the
main functions that are inside it pertaining to its subviews.
However in this case, it is being handled inside its subviews subview, 'PhotoView'. Should I use protocols to persist it to the ListViewController? PhotoView(Protocol) -> ListView(Protocol) -> ListViewController?
Is this a correct method? Or is there a more efficient way of handling subview functions?
Thanks guys
If you move all logics into ListViewController, everything is very simple as you can assign any UIControl in the VC:
class ListViewController : UIViewController{
#IBOutlet var goButton: UIButton! //In the DetailView
#IBOutlet var photoView: PhotoView!
#IBOutlet var detailView: DetailView!
// MARK: --
#IBAction func testCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
// MARK: --
#IBAction func photoViewCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
// MARK: --
#IBAction func detailViewCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
}
You don't need to even know the ListView. Use IB can assign all UIViews , not only direct children, but all subview trees, including UIControls, like buttons and labels , as long as they lies in the scene of same UIViewController and their methods is in the same VC not in other separated views.
If you have the code of views, you can move it to the VC easily. Just one class to control all. If you need to know who is who. Adding Mark is enough for small files.

(Swift) ViewController's UI-elements can not be edited by implemented delegate method

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.

Resources