How do I refer to the title of a view controller from an embedded container view's view? - ios

I have a UIViewController (let's call it "EditViewController") which has a Container View on it (call it "ContainerView") where I switch in and out various subviews (call the one I'm most concerned with "EditDetailsView").
From the EditDetailsView I need to change the title in the navigation bar of the EditViewController. I can't seem to be able to figure out how to reference it.
From inside EditViewController I can simply make a statement like:
self.title = #"Some new title";
and it changes just fine.
But from the EditDetailsView view that is currently the subview of ContainerView nothing seems to work:
self.title = ... is obviously wrong.
super.title = ... doesn't work and seems wrong anyway.
super.super.title = ... errors out as super is not a property found on UIViewController.
Can someone please tell me how to reference the title? I'm kinda lost.
Thanks!

While digging through the parentViewController chain is possible, it is error prone and unrecommended. It is considered a bad design. Imagine you set up your view controller hierarchy in some manner, but after a few months change it a bit and now there is one level deeper. Or, you would like to use the same view controller in several different scenarios. A much better design would be to pass the new title to the container view controller using delegation. Create a delegate protocol, with a method for setting the title.
- (void)childViewController:(ChildViewController*)cvc didChangeToTitle:(NSString*)title;

I know this is an old thread, but in case someone else needs it: to avoid boilerplate code with delegation, and avoid digging into the parentViewController, I did it the other way around.
I've referenced the child view controller from the parent and got their title. So no matter which child you show, you will always get the right title.
This is in Swift 3.
So, basically, this is your parent:
class EditViewController: UIViewController {
override func viewDidLoad() {
if let child = self.childViewControllers.first {
self.title = child.title
}
}
}
And this is your child:
class ContainerView: UIViewController {
override func viewDidLoad() {
self.title = "Sbrubbles"
}
}
Another good way to avoid excess code with delegation is to use RxSwift, if you are familiar to Reactive programming.

Related

How to pass data between views. When should I use what?

I have a View-Hierarchy like this:
UIViewController (SingleEventViewController)
UIScrollView (EventScrollView)
UIView (contentView)
3xUITableView (SurePeopleTV, MaybePeopleTV, NopePeopleTV (all inherited from the same UITableView)), & all other UI-Elements
The SingleEventViewController stores one Event (passed within the initializer). (All Events are stored in Core-Data).
The three UITableViews are there for displaying the users which are participating (or not or maybe) at the Event. My question is, what are the possibilities to fill the tableViews with the data and what would you recommend in which situation.
Currently I have a property parentVC: SingleEventViewController in all Subviews and get the data like this:
override func loadUsers() {
//class SurePeopleTV
guard let parentController = parentVC else { return }
users = (parentController.thisEvent.eventSureParticipants?.allObjects as! [User])
finishedLoading = true
super.loadUsers()
}
.
func applyDefaultValues() {
//class EventScrollView
guard let parent = parentVC else { return }
titleLabel.text = parent.eventName
}
I'm new to programming but I got a feeling that I should not create a parentVC reference in all of my classes.
An object should not (ideally) know about its parent - if it does they are "tightly coupled". If you change the object's parent, your code may break. In your case, your parent object must have a thisEvent property.
You want your objects to be "loosely coupled", so the object doesn't know about a specific parent object.
In Swift, the usual ways to pass information "back up the chain" is to use the delegate design pattern… https://developer.apple.com/documentation/swift/cocoa_design_patterns or to use closures.
See also https://www.andrewcbancroft.com/2015/04/08/how-delegation-works-a-swift-developer-guide/ for info on delegation
First of all, if you create a reference to the parent ViewController make sure it is weak, otherwise you can run into memory management issues.
Edit: As Ashley Mills said, delegates the way to handle this
The recommended way to pass data between ViewControllers is using something like this
Every time a segue is performed from the view controller this function is in this function is called. This code first checks what identifier the segue has, and if it is the one that you want, you can access a reference to the next view controller and pass data to it.

How can I create an instance of a custom model with multiple viewcontrollers in Xcode with Swift 4?

I'm new to Swift and I'm sure this question is pretty basic and has been asked and answered before.
I am not using storyBoard. My main viewController is created from AppDelegate via code.
I have:
a custom class defined in a model.swift file
a main viewController (from AppDelegate) that I am using as a container
3 additional viewcontrollers as subviews of the main (not each other)
all 3 subviews are displayed simultaneously each covering 1/3 of the screen (no segues)
each viewcontroller is in a separate .swift file
I want to create an instance of my custom class in the main viewController and have all 3 of the subviews be able to reference that instance.
Each of the subview view controllers need to be able to get/set instance variables and the other subviews need to be made aware of those changes.
I think I will need to use notifications to communicate the changes to the multiple subviews - but I haven't even begun to try and figure that out yet.
If this has been asked and answered before - could someone please either provide a link - or provide me with the right search terms so that I'm able to find the answer? The only found answers I've found that come close are to use segues to pass the data back and forth.
You can use delegate pattern. Below code is assuming that you are using MVVM pattern. (It is very similar for VIPER/ReSwift patterns also)
protocol DataChangedDelegate {
func refreshData()
}
// ViewModel for FirstViewController
class FirstViewModel {
var delegate: DataChangedDelegate?
var data: Any {
didSet {
delegate?.refreshData()
}
}
//rest of the things
}
//similarly other two view models will have a delegate and on data change will call the refresh method
And your view controllers should adopt this protocol
class FirstViewController: UIViewController, DataChangedDelegate {
//view controller code
//delegate code
func refreshDate() {
//tableView.reloadDate()
//collectionView.reloadDate()
//lableView.text = viewModel.data()
}
}
And where ever you create a viewControllers and add as subView, you have to set the delegate of viewModel.
let firstViewController: FirstViewController = createFirstViewController()
let firstViewModel = FirstViewModel()
firstViewModel.delegate = firstViewController
firstViewController.viewModel = firstViewModel
mainViewController.addSubView(firstViewController.view)
Similarly for all other view controllers.
Here's how I would do it:
Create a singleton class.
Configure the singleton's properties in the the main ViewController.
Use didSet to post a Notification.
Add a listener for that Notification in your additional ViewControllers.

Is it fine to nest a UIViewController within another without using addChildViewController?

I'm trying to make a custom ContainerViewController, but due to lots of difficulties with the ViewController transitions and making everything interactive, I've decided to mimic that functionality myself.
What I basically want to do, is have a paginated UIScrollView (the HeaderView) on the top control different another UIScrollView (the ControllersView) below that contains ViewControllers as pages so that as you swipe to a new page on the HeaderView, it also swipes to the next viewcontroller on the ControllersView. This is what the setup would look like.
My question is, is there anything wrong with having the aforementioned setup? All I'll do to add the view controllers to the ControllersView is just something like: controllersView.addSubview(pagecontroller1.view).
Some posts online seem to say that "the appropriate ViewController functions won't be called" or whatever. What do I seem to be missing here? I'm guessing there's a lot of dismissing and admitting of ViewControllers that I need to call every time a ViewController is out of frame right?
To clarify the question: Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call? I'm realizing that if I were to set things up this way, I need to manage a lot of things that are usually handled automatically, but as I mentioned before, custom ContainerViewControllers have failed me and I'm going with this.
PS. If you seem to still be lost on how this will look like, see my previous question here where I originally wanted to use a Container ViewController. There's a much better mockup there.
You can add and remove VC In Container Views
For - Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call?
As, We need to call WillAppear and Disappear Func when Adding and removing a VC , Thus Try using below Functions That will Handle these Responses
I use the Two specific Functions to add and remove Controller in ContainerView/UIView/SubView in ScrollView inside a UIView
To Add
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
To Remove
private func remove(asChildViewController viewController: UIViewController)
{
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
secondContainer.willRemoveSubview(viewController.view)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
Creating Object
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
For - controllersView.addSubview(pagecontroller1.view)
Answer - Yes Approbate func wont be called if pagecontroller1 is not loaded in to memory stack, to load that you need to notify pagecontroller1 that it is going to be added to memory stack as Child View , Just as We initiate a Controller and basically notifies the Controller to get its component loaded to memory stack to get some memory allocations
For Question - Is it fine to nest a UIViewController within another without using addChildViewController?
Check apple Documentation - https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchildviewcontroller
This is necessary just as to notify the controller who is going to be added in Another Parent View as Child
Sample Project
https://github.com/RockinGarg/Container_Views.git
Or
https://github.com/RockinGarg/ContainerView-TabBar.git
If Question is Still not answered Please Tell me what Func Exactly you want to handle by yourself

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 :)

UIView doesn't change at runtime

I've had this working in other variations but something seems to elude me in the change from objective-c to swift as well as moving some of the setup into it's own class.
So i have:
class ViewController: UIViewController, interfaceDelegate, scrollChangeDelegate{
let scrollControl = scrollMethods()
let userinterface = interface()
override func viewDidLoad(){
super.viewDidLoad()
loadMenu("Start")
}
func loadMenu(menuName: String) {
userinterface.delegate = self
userinterface.scrollDelegate = self
userinterface.removeFromSuperview() //no impact
scrollControl.removeFromSuperview() //no impact
userinterface.configureView(menuName)
view.addSubview(scrollControl)
scrollControl.addSubview(userinterface)
}
}
This sets everything up correctly but the problem occurs when I change loadMenu() at runtime. So if the user calls loadMenu("AnotherMenu") it won't change the UIView. It will call the right functions but it won't update the view. Although if I call loadMenu("AnotherMenu") at the start, the correct menu will display. Or if I call loadMenu("Start") and then loadMenu("AnotherMenu") then the menu displayed will be "AnotherMenu". As in:
override func viewDidLoad(){
super.viewDidLoad()
loadMenu("Start")
loadMenu("AnotherMenu")
}
When I list all the subviews each time loadMenu() is called, they look correct. Even during runtime. But the display is not updated. So something isn't getting the word. I've tried disabling Auto Layout after searching for similar issues but didn't see a difference.
Try adding setNeedsDisplay() to loadMenu
Eg
func loadMenu(menuName: String) {
userinterface.delegate = self
userinterface.scrollDelegate = self
userinterface.removeFromSuperview() //no impact
scrollControl.removeFromSuperview() //no impact
userinterface.configureView(menuName)
view.addSubview(scrollControl)
scrollControl.addSubview(userinterface)
view.setNeedsDisplay()
}
setNeedsDisplay() forces the view to reload the user interface.
I didn't want to post the whole UIView class as it is long and I thought unrelated. But Dan was right that he would need to know what was going on in those to figure out the answer. So I created a dummy UIView class to stand in and intended to update the question with that. I then just put a button on the ViewController's UIView. That button was able to act on the view created by the dummy. So the problem was in the other class. Yet it was calling the methods of the ViewController and seemingly worked otherwise. So then the issue must be that its acting on an instanced version? The way the uiview class worked, it uses performSelector(). But in making these methods into their own class, I had just lazily wrote
(ViewController() as NSObjectProtocol).performSelector(selector)
when it should have been
(delegate as! NSObjectProtocol).performSelector(selector)
so that was annoying and I wasted the better part of a day on that. But thanks again for the help.

Resources