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
}
}
Related
I'm trying to implement data prefetching for my UICollectionView using the UICollectionViewDataSourcePrefetching protocol; however, the respective method is not being called.
The collection view has a custom layout. I have also tried with the regular flow layout, same results.
Upon data reload, I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:
func reloadData() {
viewDidLayoutSubviews()
collectionView.reloadData()
collectionView.layoutIfNeeded()
viewDidLayoutSubviews()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
collectionView.layoutIfNeeded()
view.layoutIfNeeded()
}
Maybe that has something to do with it?
What I have done:
my UIViewController does inherit from UICollectionViewDataSourcePrefetching
collectionView.prefetchDataSource = self (also tried using storyboard)
collectionView.isPrefetchingEnabled = true (also tried using storyboard)
I have implemented collectionView(_:prefetchItemsAt:)
Issue:
The prefetchItemsAt method is not being called. I determined that by placing a print statement in the method & setting up a breakpoint.
Like requested in the comments, I'll share my implementation for this issue here:
I created the tracker prefetchState which determines whether I'm prefetching at the moment, or not:
enum PrefetchState {
case fetching
case idle
}
var prefetchState: PrefetchState = .idle
Then, I hooked up the scroll view's delegate (the scroll view my collection view is in) to my view controller and implemented the scrollViewDidScroll method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == self.scrollView else { return }
let prefetchThreshold: CGFloat = 100 // prefetching will start 100pts above the bottom of the scroll view
if scrollView.contentOffset.y > scrollView.contentSize.height-screenBounds.height-prefetchThreshold {
if prefetchState == .idle {
prefetchItems()
}
}
}
In there, you can see that I check whether we're already prefetching. If not, I call prefetchItems(), as implemented here:
func prefetchItems() {
guard prefetchState == .idle else { return }
prefetchState = .fetching
someDownloadFuncWithCompletionBlock { (newItems) in
self.dataSource += newItems
self.collectionView.reloadData()
self.prefetchState = .idle
}
}
I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:
This sounds very broken.
Why are you doing this?
Prefetching on the collection view (from the docs) is triggered when the user scrolls the collection view. By making the collection view frame the same as the content size you are essentially disabling the scrolling of the collection view itself.
The collection view calls this method as the user scrolls
If you are forcing the collection view frame to be the same as the content size then you are entirely breaking UICollectionView.
The reason the prefetch isn't called is because every cell has been loaded already. Nothing is in prefetch any more. Because your collection view is displaying every cell at the same time.
If you want to scroll a collection view... let the collection view handle it. You shouldn't need to place the collection view inside another scroll view.
I have a UIStackView, created in Interface Builder that contains two views. Now I'm trying to programatically change the order of these views (exchange their positions, so to speak).
Is there an easy fix for this that DOES NOT use Interface Builder to accomplish this?
Thanks :).
Create a layout with 2 UIViews in a UIStackView like so:
Create an outlet for your UIStackView to your UIViewController. Call addArrangedSubview on your UIStackView in viewDidLoad to change the order. See snippet below.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Switches order of the stackView
stackView.addArrangedSubview(self.stackView.subviews[0])
}
}
This should be the result:
The addArrangedSubview takes the given view and adds it to the end of the arrangedSubviews array. In our case we take the red view at index[0] and add it on the end (right) of the UIStackView.
You can change the view semantic of UIStackView from Unspecified to Force Right-to-Left to swap the position of of nested view.
you can use addArragnedSubview to reorder the views.
Only addArragnedSubview is needed because when adding the view to a new parent it is removed from the old parent.
if self.viewsSwitched{
stackview.addArrangedSubview(self.stackview.subviews[0])
self.viewsSwitched = false
}else{
stackview.addArrangedSubview(self.stackview.subviews[1])
self.viewsSwitched = true
}
This code exchanges the two views inside the stackview by each call
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 a document window that contains a number of NSView subclasses, switched between using a tab control. Each of the subclasses, and the window's ViewController, support different user actions accessed through menu items tied to the First Responder.
I'd like to perform a segue from one of those views in response to a menu item. However, NSView does not support performSegueWithIdentifier, it appears to be something that is part of NSViewController alone.
Can someone suggest a way around this? I have seen suggestions to pass the VC into the views, but I am not clear how to do that. Or perhaps there is a better way?
view.containingController.performSegue()
note: you have to add containingController to your views
I WOULD add the viewController to the responder chain and then make containingController a computed property in an extension!
e.g. add vc as responder:
override func awakeFromNib() {
super.awakeFromNib()
self.nextResponder = self.view
for subview in self.view.subviews {
subview.nextResponder = self
}
}
e.g. containingController in extension
extension NSView {
var containingController: NSViewController? {
get {
while(self.nextResponder != nil) {
if(self.nextResponder is NSViewController) {
return self.nextResponder
}
}
return nil
}
}
}
You could do that (see Daij-Djan's answer), however it is not what I would recommend, since a hypothetical programmer who will be using your code, but is not familiar with it (let's say, you in a year :) ) might be caught by surprise by such behaviour.
I would recommend you to add a delegate (conforming to your custom protocol, let's call it MyViewDelegate) to your NSView with a method like viewRequiresToPerformTransition(view: YourViewSubclass). Then you implement this method (more generally, you conform to MyViewDelegate protocol) in your view controller and inside its implementation perform any segue you want.
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
}
}
}