UIVisualEffectView disappears after adding subview - ios

I have a xib that I've created in Xcode's interface builder. Inside the xib is a UIVisualEffectView w/ vibrancy. If I add a subview to the UIVisualEffectView in any capacity (either in interface builder or programmatically as you can see in the code below), then the subview is vibrant (as it should be) however the UIVisualEffectView is no where to be seen.
So the subviews of the UIVisualEffectView are the only elements visible on screen while the UIVisualEffectView itself is missing.
Before adding any subviews, the UIVisualEffectView appears perfectly fine onscreen.
How can I fix this?
import UIKit
class EmptyContentView: UIView {
#IBOutlet var visualEffectView: UIVisualEffectView!
override func awakeFromNib() {
var vibrantLabel = UILabel()
vibrantLabel.text = "Vibrant"
vibrantLabel.sizeToFit()
vibrantLabel.center = self.center
self.visualEffectView.contentView.addSubview(vibrantLabel)
}
}
Edit: Note, I am testing on a device. iPhone 6 plus running iOS8. In the simulator I see neither the subviews nor the UIVisualEffectView.
Here is how I am instantiating the nib's view.
var nibViews = NSBundle.mainBundle().loadNibNamed("EmptyContentView", owner: self, options: nil)
let emptyContentView = nibViews[0] as! EmptyContentView
emptyContentView.frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: self.tableView.frame.size.height)
self.tableView.addSubview(emptyContentView)
UIView.animateWithDuration(0.2, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { self.tableView.alpha = 1 }, completion: nil)
By the way, animating the tableView or not does not make a difference. The frame for the emptyContentView is also correct.
Note: Turning off vibrancy does fix the issue.

Related

How could a tableview.window is nil when i use tableViewController? [duplicate]

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
}
}

adding a constraint to a subview makes background color not display

So i am using a custom function to format an subview that I am adding to a UICollectionViewCell. It is from Brian Voong's public project here: https://github.com/purelyswift/facebook_feed_dynamic_cell_content/blob/master/facebookfeed2/ViewController.swift.
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerate() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
What is interesting, is that in my UICollectionView I add a SubView to a single cell, and set the background color to white. The background is white when I comment out the line which sets the background for the subview, and no background color is set when I uncomment out the line setting the visually formatted constraints for the subview.
Here are the two lines which clobber each other:
func chronicleOneClicked(sender: UIButton) {
point1view.backgroundColor = UIColor.whiteColor()
addSubview(point1view)
//When the below is commented the background of point1view disappears
//addConstraintsWithFormat("|-50-[v0]-50-|", views: point1view)
}
when I do print(subviews) i see that the UIView with the white background color is the highest in the view stack (top of the stack). When i print out subviews[subviews.count-1].backgroundColor I get the Optional(UIDeviceWhiteColorSpace 1 1) which is what I expect. it is strange because the color is not displayed.
I am not sure how to go about seeing what is happening behind the scenes to confirm that the background is being set at all in the latter case.
This all happens in a class for the UiCollectionViewCell which I am using as the class of one of my UICollectionView Cells which can be viewed in its entirety here:
https://gist.github.com/ebbnormal/edb79a15dab4797946e0d1f6905c2dd0
Here is a screen shot from both cases, the first case is where the line addConstraintsWithFormat is commented out, and the second case is where it is uncommented: The subview of point1subview is highlighted with a white background in the first case.
This is how I setup the views. It all happens in a class that overrides UICollectionViewCell
class myClass : UICollectionViewCell {
var chronicle: BrowsableChronicle? {
didSet{
//etc.
point1.addTarget(self, action: #selector(chronicleOneClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let point1 : PointButtonView = {
let pointView = PointButtonView(frame: CGRectMake(0, 0, 25, 25 ))
return pointView
}()
//NOTE here is where I create the view, whose background doesn't display
let point1view : UIView = {
let pointView = UIView(frame: CGRectMake( 0, 0, 200, 270))
pointView.backgroundColor = UIColor.whiteColor()
let title = UILabel(frame: CGRectMake(0, 0, 200, 21))
title.font = UIFont(name:"HelveticaNeue-Bold", size: 16.0)
pointView.addSubview(title)
let summary = UILabel(frame: CGRectMake(0, 0, 190, 260))
summary.lineBreakMode = NSLineBreakMode.ByWordWrapping
summary.numberOfLines = 4
summary.font = UIFont(name:"HelveticaNeue", size: 12.5)
pointView.addSubview(summary)
let button = UIButton(frame: CGRectMake(0, 200, 190, 30))
button.backgroundColor = UIColor(red:0.00, green:0.90, blue:0.93, alpha:1.0)
pointView.addSubview(button)
pointView.tag = 100
return pointView
}()
//NOTE: here is where I add the subview to the UICollectionViewCell view
func chronicleOneClicked(sender: UIButton){
addSubview(point1view)
addConstraintsWithFormat("H:|-20-[v0]-20-|", views: point1view)
//TODO anytime i add a constraint here the background color leaves!
print(subviews[subviews.count-1].backgroundColor) //Prints white
}
}
UPDATE: I thought maybe it was related to this issue :
UITableViewCell subview disappears when cell is selected
Where the UICollectionViewCell is selected, and therefore iOS automatically sets the backgroundColor to clear. The problem is, that I implemented this class extension of UIView to see when didSet is called on the backgroundColor and when it is set to clear, i set it to white. However, it only calls didSet on the backgroundColor once, when i first set the color of the view. Here is the code I used to override the UIView class:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
print("background color is being set")
if backgroundColor == UIColor.clearColor() {
print("set to a clear color")
backgroundColor = UIColor.whiteColor()
}
}
}
}
The difference you are seeing is obviously caused by a view frame resulting in zero width or zero height.
Let's explain how the drawing system works.
Every view has a layer that draws its background color in its bounds, which are specified by the view frame. Then every subview is drawn. However, the subviews are not limited by the frame unless you set UIView.clipsToBounds to true.
What you are seeing means the a container view has a zero frame (either width or height) but its subviews have correct frame, therefore they are displayed correctly.
There are multiple reasons why this could happen, for example:
You are setting translatesAutoresizingMaskIntoConstraints to false to some system view (e.g. the content view of the UICollectionView).
You have a constraint conflict, resulting in some important constraint to be removed (you should see a warning).
You are missing some constraints. Specifically, I don't see you setting vertical constraints.
You should be able to debug the problem using the view debugger in Xcode. Just open your app, click the view debugger button and print the recursive description of the cell. You should see a frame that is zero.

How to display UIView over keyboard in iOS

I want to create a simple view over keyboard, when users tap "Attach" button in inputAccessoryView.
Something like this:
Is there an easy way to do it? Or i should create my custom keyboard?
You can add that new subview to your application window.
func attach(sender : UIButton)
{
// Calculate and replace the frame according to your keyboard frame
var customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.redColor()
customView.layer.zPosition = CGFloat(MAXFLOAT)
var windowCount = UIApplication.sharedApplication().windows.count
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
}
Swift 4 version:
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height - 300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
UIApplication.shared.windows.last?.addSubview(customView)
The trick is to add the customView as a top subview to the UIWindow that holds the keyboard - and it happens to be the last window in UIApplication.shared.windows.
Swift 4.0
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(MAXFLOAT)
let windowCount = UIApplication.shared.windows.count
UIApplication.shared.windows[windowCount-1].addSubview(customView)
As Tamás Sengel said, Apple's guidelines does not support adding a view over the keyboard. The recommended way to add a view over keyboard in Swift 4 & 5 is:
1) Add view with your "Next" button in your storyboard as external view and connect in your class (see Explain Image), in my case:
IBOutlet private weak var toolBar: UIView!
2) For the textfield you want to add your custom view over keyboard, add it as accessory view in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
phoneNumberTextField.inputAccessoryView = toolBar
}
3) Add action for "Next" button:
#IBAction func nextButtonPressed(_ sender: Any) {
descriptionTextView.becomeFirstResponder()
// or -> phoneNumberTextField.resignFirstResponder()
}
Explain Image:
Method 2: Result with image
In TableView Controller - add stricked view at bottom
Please follow this great link to handle safe area for screens like iPhone X if you want to use this method(2). Article: InputAccessoryView and iPhone X
override var inputAccessoryView: UIView? {
return toolBar
}
override var canBecomeFirstResponder: Bool {
return true
}
Do you have find some effective method to solve this problem? In iOS9,you put your customView on the top of the windows:
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
But if the keyboard dismisses, the top Windows will be removed, so your customView will be removed.
Looking forward for your help!
Thank you for your help!
You can definitely add the view to your application’s window, and you can also add another window entirely. You can set its frame and level. The level could be UIWindowLevelAlert.
While this can be possible with accessing the topmost window, I would avoid doing this, as it clearly interferes with Apple's guidelines.
What I would do is dismissing the keyboard and replacing its frame with a view with same dimensions.
The keyboard's frame can be accessed from keyboard notifications listed here, their userInfo contain a key that can be accessed with UIKeyboardFrameEndUserInfoKey.

TableViewController not receiving touches

NOTE: I originally asked this question wondering why didSelectRowAtIndex was not getting called. Digging into it further, I've realized that my core problem is really with one of my views not receiving touch events. I am going to leave this question here for posterity and ask a more clarified version of it. If an admin would like to close or delete this question as appropriate, please go ahead and do so.
I am implementing a sidebar in my app (like a hamburger menu) and I'm using a UITableViewController to do it. I can get the sidebar to show up and cells are initializing correctly.
My code is pretty simple, I am including all of it here. Right now I just have a view controller for the main screen and a table view controller for the sidebar. Any help is appreciated!
Update: it looks like the problem is not that didSelectRowAtIndexPath is not being called -- it's that my SimpleTableViewController is not receiving any touches at all! I tried overriding 'touchesBegan:withEvent:' in both the ViewController and the SimpleTableViewController: the ViewController receives touches just fine, but the table view controller appears to receive nothing.
Further update: I've realized that the issue has to do with how I am doing the animation to reveal the tableview on the right hand side of the screen. Right now I am "pushing" the main application view to the left, which "pulls" the containerView along with it. When I do that, no touches go to the containerView when I tap on it.
However! If I "pull" the containerView on top of the main view, touches are received just fine. Maybe I am missing something elementary here regarding what iOS considers to be the "active" part of the screen where touches are legal?
Code here:
Before -- BROKEN
#IBAction func push() {
// containerView is "pulled" alongside self.view
UIView.animateWithDuration(3.0) {
self.view.frame = CGRectMake(self.originalX - 250, self.originalY, self.view.frame.width, self.view.frame.height)
}
}
Here is a gif showing what the app looks like when touches don't work.
After -- WORKS
#IBAction func push() {
// containerView is "pulled" on top of self.view
UIView.animateWithDuration(3.0) {
self.containerView.frame = CGRectMake(self.originalX - 250, self.originalY, 250, self.frameHeight)
}
}
Here is a gif showing what the app looks like when touches do work. I added some code to change the background color of the main view to illustrate that touches are being received.
Code follows - I previously had my entire implementation here but I've redacted it to include only the relevant parts.
First the main view controller:
import UIKit
class ViewController: UIViewController, SimpleTableViewControllerDelegate {
var x = UIView()
var y = UIView()
var containerView = UIView()
let animationDuration = 1.5;
let delayTime = 0.25;
let animationTime = 0.25;
var tableViewController = SimpleTableViewController()
override func viewDidLoad() {
super.viewDidLoad()
originalX = self.view.frame.origin.x
originalY = self.view.frame.origin.y
frameHeight = self.view.frame.height
frameWidth = self.view.frame.width
containerView.frame = CGRectMake(frameWidth, originalY, 250, frameHeight)
containerView.backgroundColor = UIColor.magentaColor()
containerView.clipsToBounds = false
view.addSubview(containerView)
tableViewController.items = ["Lemon", "Lime", "Agave"]
tableViewController.delegate = self
tableViewController.tableView.dataSource = tableViewController
tableViewController.tableView.delegate = tableViewController
tableViewController.tableView.frame = containerView.bounds
tableViewController.tableView.backgroundColor = UIColor.lightGrayColor()
tableViewController.tableView.scrollsToTop = false
tableViewController.tableView.separatorStyle = .None
tableViewController.tableView.contentInset = UIEdgeInsets(top: 64.0, left: 0, bottom: 0, right: 0)
tableViewController.tableView.reloadData()
addChildViewController(tableViewController)
containerView.addSubview(tableViewController.tableView)
tableViewController.didMoveToParentViewController(self)
}
func didSelectRowAtIndexPath(indexPath: NSIndexPath) {
NSLog("[ViewController] invoked didSelectRowAtIndexPath with \(indexPath.row)")
}
var originalX : CGFloat!
var originalY : CGFloat!
var frameHeight : CGFloat!
var frameWidth : CGFloat!
#IBAction func push() {
UIView.animateWithDuration(animationTime, delay: 0, options: .CurveLinear, animations: {
self.view.frame = CGRectMake(self.originalX - 250, self.originalY, self.view.frame.width, self.view.frame.height)
}, completion: { (var b) -> Void in
})
}
#IBAction func pull() {
UIView.animateWithDuration(animationTime, delay: 0, options: .CurveLinear, animations: {
self.view.frame = CGRectMake(self.originalX, self.originalY, self.view.frame.width, self.view.frame.height)
}, completion: { (var b) -> Void in
})
}
}
For what it's worth, if you'd like to see the whole app (it's just a learning app) you can go here: https://github.com/bitops/sagacious-quack
When you add another view controller's view to your view hierarchy, you have to let both parent and child know, so the parent can forward relevant messages to the child. This is explained in Apple's View Controller Programming Guide, in the "Implementing a Custom Container View Controller" section.
In your case, you need something like this:
[self addChildViewController:tableViewController];
containerView.addSubview(tableViewController.tableView)
[tableViewController didMoveToParentViewController:self];
In viewDidLoad() of ViewController:
//Add these two lines
tableViewController.tableView.dataSource = tableViewController
tableViewController.tableView.delegate = tableViewController
tableViewController.delegate = self
tableViewController.tableView.frame = containerView.bounds
tableViewController.tableView.backgroundColor = UIColor.lightGrayColor()
tableViewController.tableView.scrollsToTop = false
tableViewController.tableView.separatorStyle = .None
tableViewController.tableView.contentInset = UIEdgeInsets(top: 64.0, left: 0, bottom: 0, right: 0)
tableViewController.tableView.reloadData()

Cannot Change Properties of Controls inside an UIView that was Loadedfrom NIB

I am not sure why this is happening.
In my application, I have
ViewController.swift
CustomUIView.swift
CustomUIView.xib
In my ViewController.swift
var customView: CustomUIView = NSBundle.mainBundle().loadNibNamed("CustomUIView", owner: self, options: nil).first as CustomUIView;
customView.setupControl();
self.view.addSubview(customView);
In my CustomUIView.swift I tried to change the properties of a UIButton created in the CustomUIView.XIB such as this
#IBOutlet weak var button: UILabel!
func setupControl()
{
label.frame = CGRect(x: 0 , y: 30, width: 320, height: 240); //THIS DOES NOT WORK
label.text = "BLABLABLA"; //THIS WORKS
}
I can only modify the property of the UIButton from the InterfaceBuilder.
Any thoughts on why this is happening?
Are you using Auto Layout? If yes, you can't set the frame, because the constraints will override the frame setting. To change the frame, you must set the constraints.

Resources