I have 2 UIViewControllers which contain Tables: A and B. Tapping a row in the table in A segues to B.
At the bottom of each view of A and B, I have a ContainerView which points to the same UIViewController say Z. I use Z to show banner-ads. The issue I have is each time my view changes (from A to B or B to C), the UIViewController Z gets re-instantiated as it should. But this is what I don't want. I want to use the same instance of the ContainerView everywhere. I did keep my ad-banners static so they are the same everywhere, but still managing orientation-changes and banner-views is getting messy. Also it makes the ad-banner disappear and re-appear when I switch my view, as the container-view instance is switched.
Is there a way that I can keep the same instance of the entire ContainerView in all my UIViewControllers A and B and anyother viewcontrollers I add ?
There are two approaches which will accomplish this task.
First Approach: Realize that it's your A, B, & C view controllers which should be in the container rather than the banner add view controller. Optionally, make a parent view controller with two containers--one for the banner ads, the other for the A, B, & C controllers.
Second Approach: When segueing from A to B to C, simply pass this view controller along. You could extraordinarily simplify this by given them all a single common parent.
class BannerViewController { /* blah */ }
class BannerViewDisplayViewController {
#IBOutlet var bannerView: UIView!
var bannerViewController: BannerViewController! {
didSet {
bannerView = bannerViewController.view
bannerViewController.didMoveToParentViewController(self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
if bannerViewController == nil {
// instantiate a bannerViewController
}
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if let destination = segue.destinationViewController as? BannerViewDisplayViewController {
destination. bannerViewController = self. bannerViewController
}
}
}
Related
I created a UIView class and implemented it inside a viewcontroller. However, I have a some other things in the view controller that I want to be able to interact with but I cannot do that because of the attached view.
how do I interact with the base viewcontroller while the custom uiview is present in the viewcontroller
var tripView: TripView!
override func viewDidLoad() {
super.viewDidLoad()
tripView = TripView(frame: CGRect.zero)
self.view.addSubview(tripView)
// AutoLayout
tripView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero)
}
You should assign proper index to your views by using following methods from UIView.
Instance Methods: aboveSubview and belowSubview or insertSubview
.
From UIView overview
Views can be nested inside other views to create view hierarchies, which offer a convenient way to organize related content. Nesting a view creates a parent-child relationship between the child view being nested (known as the subview) and the parent (known as the superview). A parent view may contain any number of subviews but each subview has only one superview. By default, when a subview’s visible area extends outside of the bounds of its superview, no clipping of the subview's content occurs.
Hope it helps!
You can do
class TripView:UIView {
// add this
class TransView:UIView {
var imgV1:UIImageView!
var imgV2:UIImageView!
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return imgV1.frame.contains(point) || imgV2.frame.contains(point) // or ![imgV1,imgV2].filter{$0.frame.contains(point)}.isEmpty
}
}
I want to use the NSStackView to stack views above each other, I also want them to de able to expand so I can't use the NSCollectionView if i understood it correctly.
So, in storyboard, I've created a NSStackView(embedded in scroll view) in the main view controller and a view controller that I want to fill it with:
The button will fill the stack view with ten views:
#IBOutlet weak var stackView: NSStackView!
#IBAction func redrawStackView(_ sender: Any) {
for i in 0..<10 {
let stackViewItemVC = storyboard?.instantiateController(withIdentifier: "StackViewItemVC") as! StackViewItemViewController
stackViewItemVC.id = i
stackView.addArrangedSubview(stackViewItemVC.view)
}
}
And the ViewController on the right simply looks like this:
class StackViewItemViewController: NSViewController {
var id: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
#IBAction func buttonPressed(_ sender: Any) {
debugPrint("StackViewItemViewController" + id.description + "pressed")
}
Running this small application works fine, every time I press the button ten more stack view items appears. But, when I have the audacity to press one of the buttons to the right the application crashes:
Where am I going wrong?
I have tried to work around the IBAction to verify that this what breaks, and the application will not crash if I subclass the button and make a "buttonDelegate" protocol with a function being called from mouseUp.
I guess the problem is that the viewController objects, which you create in the loop, are released immediately.
Even though the view is attached to the stackView, it's viewController is destroyed.
You can fix this issue by keeping a reference to each viewController.
You can do this by creating a new variable
var itemViewControllers = [StackViewItemViewController]()
and then add each newly created viewController to it:
itemViewController.append(stackViewItemVC)
I have a DotBoss:UIViewController
There are a dozen UIView in the scene, Dot:UIView
(Some are direct subviews, some are further down.)
There are even container views in the chain between the highest controller and the Dot items.
The dozen Dot items know if they are tapped...
class Dot:UIView
{
private var tap:UITapGestureRecognizer? = nil
override func awakeFromNib()
{
tap = UITapGestureRecognizer(target:self,
action: #selector(SnapDot.handleTap(_:)))
self.addGestureRecognizer(tap!)
}
func handleTap(g:UITapGestureRecognizer)
{
print("user tapped on this particular Dot...")
}
}
I want DotBoss to know when one of the Dot is tapped.
class DotBoss:UIViewController
{
func oneDotWasClicked(d:Dot)
{
}
}
How to do this perfectly in Swift?
Note for anyone googling here, this bubbling extension is remarkably useful: https://blog.veloxdb.com/2016/05/12/bubbling-events-using-uiresponder-in-swift/
A Dot (UIView) and its ultimately controlling DotBoss (UIViewController) are both responders, and are links along the responder chain. Therefore, a Dot can call a method in its DotBoss by walking the responder chain until it comes to the DotBoss:
func handleTap(g:UITapGestureRecognizer) {
// ... other stuff can go here if necessary ...
var r : UIResponder = self
repeat { r = r.nextResponder()! } while !(r is DotBoss)
(r as! DotBoss).oneDotWasClicked(self)
}
The main ViewController is embedded in a UINavigationController subclass, and the VC has a subview that is loaded from a nib. The subview is called MenuView, and contains UIButtons that will link to other VCs.
To keep my main ViewController less unruly, I have put all these buttons into a subview that loads from a nib that animates the menu opening and closing.
However, I would like to present other view controllers from these, sometimes "Modally", sometimes "Show". What I have done seems to work, but I just want to know if this is alright, or if I have caused some unwanted effects that I'm unaware of (like a strong reference cycle that would cause a memory leak, or something). Or is there a better way to do this?
Some code:
In MenuView.swift
class MenuView: UIView {
var navigationController = CustomNavigationController()
func combinedInit(){
NSBundle.mainBundle().loadNibNamed("MenuViewXib", owner: self, options: nil)
addSubview(mainView)
mainView.frame = self.bounds
}
#IBAction func optionsAction(sender: AnyObject) {
self.navigationController.performSegueWithIdentifier("presentOptions", sender: self)
}
In ViewController.swift
menuView.navigationController = self.navigationController as! CustomNavigationController
Short answer: No, it is not alright to access a view controller from within some view in the hierarchy, because that would break all the MVC rules written.
UIView objects are meant to display UI components in the screen and are responsible for drawing and laying out their child views correctly. That's all there is. Nothing more, nothing less.
You should handle those kind of interactions between views and controllers always in the controller in which the view in question actually belong. If you need to send messages from a view to its view controller, you can make use of either the delegate approach or NSNotificationCenter class.
If I were in your shoes, I would use a delegate when view needs some information from its view controller. It is more understandable than using notification center as it makes it much easier to keep track of what's going on between. If the view controller needs some information from a view (in other words, the other way around), I'd go with the notification center.
protocol MenuViewDelegate: class {
func menuViewDidClick(menuView: MenuView)
}
class MenuView: UIView {
var weak delegate: MenuViewDelegate?
#IBAction func optionsAction(sender: AnyObject) {
delegate?.menuViewDidClick(self)
}
}
Let's look at what's going on at the view controller side:
class MenuViewController: UIViewController, MenuViewDelegate {
override func viewDidLoad() {
...
self.menuView.delegate = self
}
func menuViewDidClick(menuView: MenuView) {
navigationController?.performSegueWithIdentifier("presentOptions", sender: self)
}
}
For more information about communication patterns in iOS, you might want to take a look at this great article in order to comprehend how they work.
I have been playing with the concept of the parent/child view delegation for a few days now, and currently understand how to feed data from parent to child. However, now, I want a button in the parent (main VC) to reload the data presented in the child VC.
I'm trying to delegate a method that is activated in the child VC's class but is activated in the parent's navigation controller. So that when I press the button, the delegated method in the child VC is performed; in my case, that method would be reload table. Why am I getting so many errors when trying to set up this simple delegation relationship?
My parent/container View is currently delegating a method to the child, so I have it set up from child -> parent. But I want to set it up from parent -> child. Pretty much I have:
struct Constants {
static let embedSegue = "containerToCollectionView"
}
class ContainerViewController: UIViewController, CollectionViewControllerDelegate {
func giveMeData(collectionViewController: CollectionViewController) {
println("This data will be passed")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let childViewController = segue.destinationViewController as! CollectionViewController
childViewController.delegate = self
}
}
FROM CHILD:
protocol CollectionViewControllerDelegate {
func giveMeData(collectionViewController: CollectionViewController)
}
class CollectionViewController: UIViewController {
var delegate:CollectionViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.giveMeData(self)
// Do any additional setup after loading the view.
}
I think my trouble is the fact that I'm declaring the child delegate in a prepareforsegue, so that was straight forward, but now I want the reverse delegation. How do I set that up so that I can use a child-method from the parent VC?
The child view controller has no business supplying other controllers with data. It should actually not even have any data fetching logic that is so generic it is also used by other controllers. You should refactor the data methods out into a new class.
This pattern is called Model-View-Controller, or MVC, and is a very basic concept that you should understand and follow. Apple explains it pretty well.
In general, to send data to from a controller to a detail controller, use prepareForSegue to set properties, etc. To communicate back to the parent controller, you use delegate protocols, but usually these are called when the detail controller is finished with its work and just reports the result up to the parent.
If you want to update the detail VC with new data (without dismissing it and with the parent not visible) you should not put the logic to update it into the parent. Instead, use the structure suggested above.