Check if subview exists in swift 1.2 - swift

I have added my subview using
self.view.addSubview(noticeSubView)
At some point I need to check if that subview exists before proceeding with some other actions etc. I found the following while searching but not sure if it is how it is done and also how to implement.
BOOL doesContain = [self.view.subviews containsObject:pageShadowView];
I appreciate any help, thanks.

Rather than asking if a subview exists in your view, you are better off asking if the subview (in your case noticeSubView) has a parent that is your view.
So in your example you would check later for:
if ( noticeSubView.superview === self.view ) {
...
}
The triple "===" is making sure that the superview object is the same object as self.view, instead of trying to call isEqual() on the view.
Another approach people have used in the past is to set an integer tag on a subview, like so:
noticeSubView.tag = 4
The default is zero so anything nonzero will be unique.
Then you can check to see if a superview contains a specific view by tag:
if ( self.view?.viewWithTag(4) != nil )
...
}
If you take that approach, you should probably create an enum for the integer value so it's clearer what you are looking for.
Note: There's a "?" after self.view because a view controller will not have a view defined until after viewDidLoad, the "?" makes sure the call will not occur if self.view returns .None

If noticeSubView is a custom class (let's call it NoticeSubView), then you can find it like this:
for view in self.view.subviews {
if let noticeSubView = view as? NoticeSubView {
// Subview exists
}
}
Or, you can assign a tag to the view and search for it.
noticeSubView.tag = 99
//...
for view in self.view.subviews {
if view.tag == 99 {
// Subview exists
}
}

best and easy way to find that your view is exist or not , there are many way like you check a view is contain or not in super view or just view some time this method is failed because if the Uiview is already remove then error occur, so code is here : here errorView is my UiView
errorView.tag = 333
if ( self.view?.viewWithTag(333) != nil ){
print("contain")
}
else {
print("not contain")
}

Related

Swift: addGestureRecognizer not work for stackview children

Codes:
for ... {
let view = CategoryClass.createMyClassView()
view.myLabel.text = packTitle
view.twoLabel.text = packText
view.bgCaategory.layer.cornerRadius = 30
i = i + 1
if(i == 1){
selectPackId = packId!;
view.imgSelect.image = UIImage(named: "selected")
} else {
view.imgSelect.image = UIImage(named: "select")
}
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSendData(sender:))))
self.stackView.addArrangedSubview(view)
}
#objc func handleSendData(sender: UITapGestureRecognizer) {
print("H 1")
}
If i click on view, nothing print "H 1"
I want if i click on view, get id or another value of view
If adding isUserInteractionEnabled as suggested by Marcel still doesn't work, also make sure that every parent view in the hierarchy has a valid frame (you can check it in Debug View Hierarchy).
E.g. it happened to me to add a UIStackView into a parent UIView but the layout constraints were not correct, so I ended up having the parent UIView frame size as 0 (but the UIStackView was still visible).
If you create the UIStackView in interface builder, the isUserInteractionEnabled property is false by default. This means that the view and all it's child views won't respond to user interaction.
When you create a view in code, this property is true be default.
Add:
stackView.isUserInteractionEnabled = true
You only have to add this once, in your viewDidLoad for example.
The reason it doesn’t work is possibly a wrong method signature. The correct signature for recognizer actions is this:
recognizerAction(_ recognizer: UIGestureRecognizer)

How to handle iOS 11 large title animation when using multiple container views?

I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.
I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.
I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.
Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?
I tried the following
1) Change Container view zPosition when changing segments
2) Move the container view to the front by calling view.bringSubview(toFront: ...)
3) Looping through the subviews and calling
view.exchangeSubview(at: 0, withSubviewAt: ...)
I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.
Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.
So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
Essentially what I did is
1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.
2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them
lazy var viewController1: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
lazy var viewController2: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.
extension UIViewController {
func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
func remove(asChildViewController viewController: UIViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
}
4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.
func didPressSegmentedControl() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: viewController2)
add(asChildViewController: viewController1)
} else {
remove(asChildViewController: viewController1)
add(asChildViewController: viewController2)
}
}
5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.
func viewDidLoad() {
super.viewDidLoad()
didPressSegmentedControl()
}
This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.
Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.
Hope this helps somebody trying to do the same.
This is a horrible issue which I hope will be resolved soon, but there is another fix - although I freely admit that this is a nasty hack.
Basically, the issue only applies to the FIRST container view in the hierarchy. Add another container view to your hierarchy. Set it as hidden, then delete its segue and its target view controller just to be neat. Finally, make sure that this is the first container view in the hierarchy by dragging it to the top of the list.

performSegueWithIdentifier from a NSView subclass?

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.

EXC_BAD_INSTRUCTION with Swift UIButton

I am using below code in a function to handle all UIButtons in my View and it works fine when all the objects in the View are UIButton.
for v in self.mainView.subviews as [UIButton]
{
println (v.tag)
}
But in case there is any other objects like Label or ImageView in the same View, I get error 'EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subdued=0x0)'.
How should I change the code so that it works only for (all) UIButton.
The subview property of UIView is an array of AnyObject. The runtime happens because you are performing a forced downcast from [AnyObject] to [UIButton].
From a compiler perspective, the down cast is legit:
let x: [UIButton] = [AnyObject]() as [UIButton]
because AnyObject can be any class, and UIButton is a class.
In your case you are making the assumption that all objects contained in subviews are instances of UIButton, which can be possible (if you explicitly add UIButtons only to the view), but if the view has other UI elements (labels, other views, etc.) then the downcast will fail at runtime.
If for example the view contains another view, the above downcast is equivalent to doing this:
var view = UIView()
var button = view as UIButton
which fails because a UIView is not a UIButton (although thanks to polymorphism the opposite is true, being UIButton inherited from UIView).
If you want your code to print the tag for all UIButtons, ignoring all other UI elements, then #rahul_send89's answer is the correct one: it loops through all elements of the subview property, and it prints the tag only if the current element is a UIButton.
#SteveRosenberg's answer instead print the tag for all elements, regardless of their actual type.
Depending on what you want to do with your buttons (I presume the code posted in your question is just placeholder to explain the problem), there's an alternate way: filtering all buttons from the subviews property and storing into an array:
var buttons = mainView.subviews.filter { $0 is UIButton } as [UIButton]
and do whatever you need with this array of UIButton, such as printing their tag.
for button in buttons {
println(button.tag)
}
This worked for me:
for v in self.view.subviews as [AnyObject]
{
println (v.tag)
}
for v in self.mainView.subviews as [AnyObject]
{
if v is UIButton{
println (v.tag)
}
}

Getting a UIView's visible rectangle

I have a UIScrollView that contains a custom UIView. Inside the custom UIView, I'd like to know the rectangle in which it's visible (i.e. not clipped).
The quick-n-dirty solution is to have the custom UIView assume that the parent is a UIScrollView and get the content size through it, but I'm looking for a better solution that doesn't involve make such assumptions.
This should do the trick
CGRect visibleRect = CGRectIntersection(self.frame, superview.bounds);
Use that in the UIView and it should get you the rectangle (if any) that represents the visible section of that view in it's superview (The UIScrollView). I'm assuming here that there is no view between them in the hierarchy, but if there is, fiddling the code should be trivial.
Hope I could help!
It would help if you would give more info on what is that you are trying to accomplish.
If you want to know the size of the super view you can do this:
CGRect superFrame = [self superview].frame;
Swift 3
extension UIView {
var visibleRect: CGRect? {
guard let superview = superview else { return nil }
return frame.intersection(superview.bounds)
}
}