Exist way notify when custom UIView will be presented or removeFromSuperview()? - swift

I am create custom UIView using this code:
lazy var temporary: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .white
return view
}()
Exist way use NotificationCenter notify when this view will be presented and removed from VC if I am use:
self.view.addSubview(temporary)
or
temporary.removeFromSuperview()

I'm not totally sure what you are asking. I think you're asking for a way to tell when your custom view is added as a subview of another view.
The easiest way to do that is to make your view a custom subclass of UIView, and implement didMoveToSuperview() or willMove(toSuperview:). Those methods get called when your view is added as a child view of another view.
If you really want to use the notification center you could have your custom view class broadcast a notification when it gets added to a superview.

Related

place NavigationBar subview behind the NavigationBarItem and title

I want to add a view below everything that's on the navigationBar. I need to add them as subviews to my NavigationBar so that they appear on any detail ViewController.
Here is what I tried so far inside my custom NavigationBar subclass (I call this method inside layoutSubviews()):
private var backgroundView = UIView()
backgroundView.frame = CGRect(x: 0, y: 0, width: 100, height: 70)
backgroundView.backgroundColor = .blue
backgroundView.alpha = 0.7
self.insertSubview(backgroundView, at: 0)
And this is what I get:
The backgroundView appears on top of the title and the NavigationBarItem.
What can I do?
According to this post on the apple developer forum, you can do this inside the NavigationBar class (which works!):
self.subviews[0].insertSubview(backgroundView, at: 0)

UICollectionView not scrolling after custom UIView added to view

I'm trying to add a custom UIView after adding a UICollectionView because I wanna show the custom bar view above the collection view. This is my code:
func loadFilters() {
let categoriesFlowLayout = UICollectionViewFlowLayout()
categoriesFlowLayout.scrollDirection = .vertical
categoriesCollection = UICollectionView(frame: CGRect(x: 10, y: getY(yAxis: searchField) + 10, width: view.frame.size.width - 20, height: (view.frame.size.height * 9 / 10) - getY(yAxis: searchField) - 10), collectionViewLayout: categoriesFlowLayout)
categoriesCollection.register(categoriesCell.self, forCellWithReuseIdentifier: "categoriesCell")
categoriesCollection.delegate = self
categoriesCollection.dataSource = self
categoriesCollection.backgroundColor = UIColor.clear
categoriesCollection.showsVerticalScrollIndicator = false
categoriesCollection.showsHorizontalScrollIndicator = false
view.addSubview(categoriesCollection)
addBar()
categoriesCollection.isUserInteractionEnabled = true
}
The addBar() function is declared in the custom superclass ViewController
if addBar() is called before view.addSubview(categoriesCollection) it looks like the image below but if it is called after then my collection view does not scroll or recognize touches. Is there anyway that will make the collection view scroll and bring the custom bar to front?
I've used sendSubviewToBack() and bringSubviewToFront() functions as well but the result is the same
When you insert a view above another view, the top view gets all touch events. So the UICollectionView does not receive any touch events anymore since another view is above it.
As I see from your post, you just want the bar at the bottom of the screen. So check the size of your custom UIView. It probably fills the entire screen and is completely above the UICollectionView. Just give the UIView some sort of background color and see, how much space it fills.
If this doesn't work, you can use a UIGestureRecognizer on the custom UIView and forward the touch events to the UICollectionView.

how to make a view scroll with the collection view using swift

I have a collection view inside of UIViewController. The view controller also has another UIView (let's call it welcomeView) which is placed above the collection view. When I scroll through the collection view, I would like the welcomeView to scroll with it. I have looked at this answer https://stackoverflow.com/a/43215801/5124961 which pretty much sums up how id like it to look, minus the view sticking to the top. The only problem with that solution is that my collectionView already has a header, so adding the welcomeView as a sublayer to collection view adds it under the already existing header which I dont want.
by just using this snippet of code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = collectionView.contentOffset.y
if(offset > 0){
self.welcomeView.frame = CGRect(x: 0, y: -offset, width: self.view.bounds.size.width, height: 100)
}else{
self.welcomeView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 100)
}
}
I get this effect:
I think the frame of the collection view has to change depending on the offset but not too sure on how to achieve this after playing around with it. I have also looked at a few other questions similar to mine, however, these were in objective C which I am not too familiar with. Does anyone have a solution? thank you.
If I guess correctly from your capture, your welcome view doesn't overlay the collection view.
Based on this guess, the solution would be:
make the collection view go to the top of your controller's view
your welcome view can overlap your collection view
to avoid that the welcome view overlaps the content of your collection view, modify the contentInset property of your collection view (the contentInset's top should be equal to the welcome view's height).
keep your snippet
the scroll indicator will probably disappear under the welcome view. A simple solution is to have a transparent background to the welcome view. Thanks to the contentInset and the delegate call, you're guaranteed that there's no overlap between the content of the collection view and the welcome view.

Custom views in Horizontal scroll view. Is it possible?

I am designing an app which has a screen in which I have a horizontal scroll view which I fill with UIViews dynamically depending upon the number of data I have in my array . I did the same via programmatically. I have mentioned my approach below.
1) I put a Scroll view for scrolling horizontally and created a reference for that in my class.
2) I programatically added views as per my code -
var imagevieww = UIImageView()
#IBOutlet weak var hrzntlscrl: UIView!
#IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad()
{
super.viewDidLoad()
let viewcount = 15
for var i = 0; i < viewcount; i++
{
let viewnew = UIView(frame: CGRectMake( hrzntlscrl.frame.origin.x+110*CGFloat(i), 0, 100.0, hrzntlscrl.frame.height))
viewnew.backgroundColor = UIColor.orangeColor()
imagevieww = UIImageView(frame: CGRectMake(0, 10, 100.0, 50))
imagevieww.backgroundColor = UIColor.blackColor()
viewnew.addSubview(imagevieww)
scrollview.addSubview(viewnew)
}
}
So I just wanted to know that instead of creating a view and the corresponding subviews eg. here imageview and setting their location and frame size programatically , Can I have a standard custom view designed in my IB and use any reference of that in my for loop instead of creating one programmatically? If we can do that,can you please give me some steps.
Yes. This is possible. You can instantiate a class from a nib with
let customView: CustomView = NSBundle.mainBundle().loadNibNamed("CustomViewNibName", owner: self, options: nil)[safe: 0] as? CustomView
You would also need to set the content size of the horizontal scroll view to the combined width of all the views.
But I think that your use case would be better served by using a UICollectionView instad of a scroll view, UICollectionView does all this and more in a much simpler implementation.

Add view over tableview (UITableViewController)

Situation: I've got a UITableViewController loading some data asynchronously from a service. During this time I would like to place a full screen (except navigation bar) view over the table view showing my custom indicator and text.
Problem: The problem I'm facing is that when my custom view (it has a red background) is placed over the UITableView the lines of the table view are shown trough my custom view (see image below).
What I tried:
I tried to use insertBelow and above, didn't work. I also tried to do: tableview.Hidden = true, but this also hides the custom view for some reason as seen on image 2.
Image1: For some reason I can see the lines threw my view.
Image 2: Tableview + custom view gone when hidden = true used.
My code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UIView view = new UIView (new RectangleF (0, 0, this.TableView.Frame.Width, this.TableView.Frame.Height));
view.BackgroundColor = UIColor.Red;
this.TableView.AddSubview (view);
TableView.Source = new SessionTableViewSource ();
}
You can use self.navigationController.view as view for adding subview.
The issue is that the View of a UITableViewController is a UITableView, so you cannot add subviews to the controller on top of the table.
I'd recommend switching from a UITableViewController to a simple UIViewController that contains a UITableView. This way the controller main view is a plain UIView that contains a table, and you can add subviews to the main UIView and they will be placed on top of the table view.
You can try to add the view to the window instead of nesting it in the table view like this:
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: overlayview];
UIWindow* window = [[UIApplication sharedApplication].delegate.window;
[window addSubview: your-overlayview];
Swift / Storyboard Solution
Note: The code below assumes one has a custom view (ratingView in my case) that is to be presented over a UITableView.
I've read many answers to this and similar questions on SO. The other answers from these sources worked to varying degrees for me (e.g.,view loaded but not shown or not accessible,...). I am using Swift 2.0+ and I am sharing the complete solution for doing this using a UITableViewController.
Create an outlet to the Navigation Bar and the view, which you want to bring over the tableview.
//MARK:Outlets
#IBOutlet weak var navBar:UINavigationBar!
#IBOutlet var ratingView: MNGStarRating!
In my case I also wanted to animate the view over the tableview so I used a class variable to hold a reference to the inflection point and a point above the scene (off-screen).
var centerYInflection:NSLayoutConstraint!
var aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)
Then in viewDidLoad I called a function (configureRatingViewAutoLayout) which configures and adds the constraints for the new view to be animated over the tableview.
func configureRatingViewAutoLayout() {
//REQUIRED
self.navBar.superview?.addSubview(self.ratingView)
var newConstraints:[NSLayoutConstraint] = []
newConstraints.append(self.ratingView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor,constant: 10))
newConstraints.append(self.ratingView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor,constant: 10))
newConstraints.append(self.ratingView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor))
//hides the rating view above the scene
self.centerYInflection = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor, constant: self.aPointAboveScene)
//the priority must be set below 1000 if you intend to change it after it has been added to a view
self.centerYInflection.priority = 750
newConstraints.append(self.centerYInflection)
//constraints must be added to the container view of the two items
self.ratingView.superview?.addConstraints(newConstraints)
}
Nota Bene - On a UITableViewController; the self.view is the
self.tableView. They point to the same thing so I guess one could also
use the self.tableView reference above.
Sometime later... In response to a UIControl event I call this method.
#IBAction func toggleRatingView (sender:AnyObject?){
//REQUIRED
self.ratingView.superview?.layoutIfNeeded()
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.37, initialSpringVelocity: 0.99, options: [.CurveEaseOut], animations: { () -> Void in
if CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away
//I play a sound to alert the user something is happening
self.centerYInflection.constant = self.aPointAboveScene
self.centerYInflection.priority = UILayoutPriority(950)
//I disable portions of the UI
self.disableUIElements(nil)
} else {
//out of frame ~ animate in
//I play a different sound here
self.centerYInflection.constant = 0
self.centerYInflection.priority = UILayoutPriority(950)
//I enable the UI fully
self.enableUIElements(nil)
}
//REQUIRED
self.ratingView.superview?.setNeedsLayout()
self.ratingView.superview?.layoutIfNeeded()
}) { (success) -> Void in
//do something else
}
}
These helper methods can be configured to control access to elements in your scene during the presentation of the view.
func disableUIElements(sender:AnyObject?) {
//UI
}
func enableUIElements(sender:AnyObject?) {
//UI
}
Caveats
My view is a custom view in the Storyboard (sitting outside of the
tableview but connected to the TableView Controller). The view has a
required user runtime attribute defined layer.zPosition with a Number value set to 2 (this ensures that it presents in front of the
UITableView).
One could also try playing around with bringSubviewToFront:
and sendSubviewToBack: methods if you don't want to set the zPosition
(I think zPosition is simpler to use)
Try this to hook a button at bottom of the UITableViewController
declare button as a variable:
var submitButton: UIButton!
and in viewDidLoad:
submitButton = UIButton(frame: CGRect(x: 5, y: UIScreen.main.bounds.size.height - 50, width: UIScreen.main.bounds.size.width - 10, height: 50))
submitButton.backgroundColor = UIColor.init(red: 180/255, green: 40/255, blue: 56/255, alpha: 1.0)
submitButton.setTitle("Submit", for: .normal)
submitButton.titleLabel?.font = UIFont(name: "Arial", size: 15)
submitButton.titleLabel?.textColor = .white
submitButton.addTarget(self, action: #selector(submit), for: .touchUpInside)
submitButton.layer.cornerRadius = 5
self.view.addSubview(submitButton)
and implement this method:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
submitButton.frame = CGRect.init(x: submitButton.frame.origin.x, y: UIScreen.main.bounds.size.height + scrollView.contentOffset.y - 50, width: submitButton.frame.width, height: submitButton.frame.height)
}
This works for me:
if let myTopView = Bundle.main.loadNibNamed("MyTopView", owner: self, options: nil)?.first as? MyTopView {
if let view = UIApplication.shared.keyWindow{
view.addSubview(myView);
myTopView.translatesAutoresizingMaskIntoConstraints = false
myTopView.topAnchor.constraint(equalTo: view.topAnchor ).isActive = true
myTopView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myTopView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myTopView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}