I'm trying to draw a a line at the top of a table view as follows:
func addTopLineToTableView() {
let layer = CALayer()
layer.backgroundColor = UIColor.green.cgColor
layer.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: 1.0)
view.layer.addSublayer(layer)
}
This works perfectly on every device except an iPhone X.
I've tried getting the safeAreaInsets and the safeAreaLayoutGuide but that didn't seem to do anything.
Any help greatly appreciated!
EDIT
Above is what the end result should look like. Here the table view has a red background and my green CALayer is right at the top as expected. It has been placed there using the function above. This is the way it looks on every device except the iPhone X.
Here is what happens when the same code is run on an iPhone X.
Like friends said before, the line can be set to the parts, you can extend the view
extension UIView {
#IBInspectable var topBorderWidth: CGFloat {
get {
return 0.0 // Just to satisfy property
}
set {
let line = UIView(frame: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: newValue))
line.translatesAutoresizingMaskIntoConstraints = false
line.backgroundColor = UIColor.green
self.addSubview(line)
let views = ["line": line]
let metrics = ["lineWidth": newValue]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[line]|", options: [], metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[line(==lineWidth)]", options: [], metrics: metrics, views: views))
}
}
}
Such use
testView.topBorderWidth = 1
Related
Firstly, I think that the problem is on the UIVisualEffectView, but now I think that the problem is on the CGAffineTransform that I'm using to animate the view on the openOptionsTooltip()
First, I found a similar problem on that question (see the comments on the accepted answer), but in my case I'm adding a button as subview of the UIVisualEffectView, not adding it as a button subview.
Apple documentation says:
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.
But I can't find a better explanation on how or why it can affect the content added to the contentView.
I'm doing all the stuff programmatically, without any xib.
The visual effect view is a balloon like a tooltip that appears when I touch the titleView.
I'm creating that view on viewDidLoad and holding a
reference to it on the viewController.
After created, when I click on the titleView I'm adding it as a subview of UIWindow
Here's my code:
The method to create the view:
func createOptionsTooltip(withNamesAndActions options: [(name: String , action: Selector)]){
self.tooltipSize = CGSize(width: 250, height: CGFloat(60*options.count+12))
var optionItens : [UIView] = []
for i in 0..<options.count{
let optionView = UIView(frame: CGRect(x: .zero, y: CGFloat(60*i), width: self.tooltipSize.width, height: 60))
optionView.backgroundColor = .clear
let separatorView = UIView(frame: CGRect(x: .zero, y: 59, width: self.tooltipSize.width, height: 1))
separatorView.backgroundColor = UIColor(white: 0.8, alpha: 0.5)
let button = UIButton(frame: CGRect(x: .zero, y: .zero, width: self.tooltipSize.width, height: 59))
button.setTitle(options[i].name, for: .normal)
button.setTitleColor(self.view.tintColor, for: .normal)
button.addTarget(self, action: options[i].action, for: .touchUpInside)
button.isEnabled = true
button.isUserInteractionEnabled = true
optionView.addSubview(button)
optionView.addSubview(separatorView)
optionView.bringSubviewToFront(button)
optionItens.append(optionView)
}
self.blackOpaqueView = UIView(frame: self.view.bounds)
self.blackOpaqueView.backgroundColor = UIColor.black
self.blackOpaqueView.alpha = 0
let gesture = UITapGestureRecognizer(target: self, action: #selector(ScheduleNavigationController.closeOptionsTooltip))
// blackOpaqueView.addGestureRecognizer(gesture)
self.vwTooltip = UIView(frame: CGRect(origin: CGPoint(x: self.view.frame.midX - self.tooltipSize.width/2, y: self.view.frame.minY+self.navigationBar.frame.size.height+UIApplication.shared.statusBarFrame.height+10), size: CGSize(width: 1, height: 1)))
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 0.01)
self.vwTooltip.isHidden = true
let vwAim = UIVisualEffectView(frame: CGRect(x: self.tooltipSize.width/2 - 6, y: 0, width: 12, height: 10))
let vwBalloon = UIVisualEffectView(frame: CGRect(x: 0, y: vwAim.frame.size.height, width: self.tooltipSize.width, height: CGFloat(60*options.count)))
vwBalloon.cornerRadius = 8
vwBalloon.contentView.isUserInteractionEnabled = true
vwBalloon.isUserInteractionEnabled = true
let bezPath = trianglePathWithCenter(rect: vwAim.frame)
let maskForPath = CAShapeLayer()
maskForPath.path = bezPath.cgPath
vwAim.layer.mask = maskForPath
vwBalloon.effect = UIBlurEffect(style: .prominent)
vwAim.effect = UIBlurEffect(style: .prominent)
for optionView in optionItens{
vwBalloon.contentView.addSubview(optionView)
vwBalloon.bringSubviewToFront(optionView)
}
self.vwTooltip.addSubview(vwBalloon)
self.vwTooltip.addSubview(vwAim)
self.vwTooltip.bringSubviewToFront(vwBalloon)
vwBalloon.bringSubviewToFront(vwBalloon.contentView)
}
And I'm calling't on the viewDidLoad that way:
createOptionsTooltip(withNamesAndActions: [("Sair", #selector(ScheduleNavigationController.closeSchedule(_:))), ("Hoje", #selector(ScheduleNavigationController.scrollToToday(_:)))])
And I'm open the tooltip animated this way:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, animations: {
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, animations: {
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
})
}
}
}
What I have tried:
Bring the buttons and all it's superview in hierarch to front using bringSubviewToFront. (I have checked, there's no view on the front of the button)
change the isUserInteractEnabled for all button superviews in hierarch.(for both false and true)
Change the way that I'm adding the target to buttons, without using the method parameter.
Change the selector method to any other method that work on other targets and switch the method between #objc and #IBOulet
Change the buttons to a view other than the UIVisualEffectView
PS: I found that the UIVisualEffectView has 3 subviews, but apparently the contentView is the above view, see the log of printing it's subviews:
▿ 3 elements
- 0 : <_UIVisualEffectBackdropView: 0x102dcd140; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x280d1b480>>
- 1 : <_UIVisualEffectSubview: 0x102dcd350; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x280d15c80>>
- 2 : <_UIVisualEffectContentView: 0x102dcc940; frame = (0 0; 250 120); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x280d1ace0>>
I can't figure out what is blocking the button's user interaction
Here's the result:
I have found the problem:
After calling the openOptionsTooltip() the transform inside't are resizing all it's subview, but the vwTooltp itself don't change it's frames, but because it clipToBounds property are false, all the subviews are showing, but the user can't access the button actions because the view has 1x1 size. Changing the openOptionsTooltip() method to that:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.vwTooltip.transform = self.vwTooltip.transform.translatedBy(x: 1, y: 1)
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
}){ _ in
self.vwTooltip.frame = self.tooltipFrame
}
}
}
}
And now it's working properly. Thanks for the responses
everybody.
I am making a slide out menu in swift. I have made everything working fine. The slide out menu shows when hamburger button is clicked and also when right swipe on the screen is done. Now I want to make the size (width) of a slide out menu change based on the user coordinates on the screen.
The slide out is implemented using collection view on top of a UIView.
To take the user's position from the screen I have used touchMoved function to get user's real-time x value. Is there any method to use this value so that collection's view's width can be changed.
Any help will be very helpful. If needed I can upload all the codes. Changing the anchor size also didn't help.
MoreOptionsViewController.swift
override init() {
super.init();
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.register(MoreOptionsCell.self, forCellWithReuseIdentifier: cellId);
getData();
}
func showMoreOptions(){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
view.setAnchor(top: window.topAnchor, bottom: window.bottomAnchor, left: window.leftAnchor, right: window.rightAnchor);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
view.alpha = 0.5;
collectionView.alpha = 1;
window.addSubview(collectionView);
let width = window.frame.width * 0.9;
//Animate the collection view's width
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.collectionView.leftAnchor.constraint(equalTo: window.leftAnchor).isActive = true;
self.collectionView.topAnchor.constraint(equalTo: window.topAnchor).isActive = true;
self.collectionView.widthAnchor.constraint(equalToConstant: width).isActive = true;
self.collectionView.heightAnchor.constraint(equalTo: window.heightAnchor).isActive = true;
}, completion: nil)
}
}
func handleTapOutside(){
collectionView.removeFromSuperview();
view.removeFromSuperview();
}
let view: UIView = {
let view = UIView();
view.backgroundColor = UIColor.black;
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout();
layout.minimumLineSpacing = 0;
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
cv.showsVerticalScrollIndicator = false;
cv.backgroundColor = UIColor(red: 59/255, green: 65/255, blue: 65/255, alpha: 0.9);
cv.translatesAutoresizingMaskIntoConstraints = false;
return cv;
}();
The above code contains the actual collection view to present the slide out menu.
If i try to change the width from touchMoved function I get a lot of error. Any help will be much appreciated.
Try setting the collection view's width by updating its frame in your touchMoved function
yourCollectionView.frame = CGRect(x: 0, y: 0, width: <user's_x_scroll_value>, height: height)
Solved the problem. The problem was caused due to the animation code.
func showMoreOptions(xValue: CGFloat){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
//Display the view beyond the screen. Increase the x coordinates according to the x value of screen
view.frame = CGRect(x: -window.frame.width + xValue , y: 0, width: window.frame.width, height: window.frame.height);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
print("View's x value is ", xValue)
view.alpha = 0.011; //Use minimum alpha value
collectionView.alpha = 1;
window.addSubview(collectionView);
//Same as above view
collectionView.frame = CGRect(x: -window.frame.width + xValue, y: 0, width: window.frame.width * 0.9, height: window.frame.height);
view.layoutIfNeeded()
collectionView.collectionViewLayout.invalidateLayout();
}
}
Also collectionView.collectionViewLayout.invalidateLayout() fixed the problem. Thank you Eli whittle for the help
How can i write the following code using NSLayoutConstraints (or anchors) in a UIView subclass?
func commonInit() {
aView = UIView()
aView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(aView)
}
override func layoutSubviews() {
super.layoutSubviews()
let smallerSide = min(bounds.width, bounds.height)
aView.bounds = CGRect(x: 0.0, y: 0.0, width: smallerSide, height: smallerSide)
aView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
One objective is to avoid using layoutSubviews().
Also, the aView must maintain a 1:1 aspect ratio.
If you'd need any more info, please let me know.
PS: Please let's use swift3, thanks.
Check the below code:
let view = UIView(frame: CGRectZero)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
super.init(frame: frame)
let viewsDict = ["view": view]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addSubview(view)
I have replicated the same behaviour via size classes, but only for iPhones. Check this storyboard. (Its not code, what you asked for)
Storyboard
Since iPads have width:Regaular and Height:Regular, it seems it is not possible for iPads, without using layoutSubview.
I have a view, say a red rectangle, and I have a subview of it, a green rectangle, all added programmatically.
Now, how can I make a green rectangle fill the whole red rectangle in width and height without getting current size of the screen, so that when red rectangle resizes on screen rotation, the green one resizes automatically too?
let redView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 350))
redView.backgroundColor = UIColor.redColor()
redView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.addSubview(redView)
let greenView = UIView(frame: redView.bounds)
greenView.backgroundColor = UIColor.greenColor()
redView.addSubview(greenView)
greenView.autoresizingMask = redView.autoresizingMask
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}
should be frame = bounds
You can use Visual Format Auto Layout like below where self == YOUR_GREEN_RECT
guard let superview = self.superview else {
return
}
self.translatesAutoresizingMaskIntoConstraints = false
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
Or just using Autoresizingmask
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
You can override the layoutSubviews() method of Red Rectangle. Inside the layout, just reassign the red rectangle frame to the green Rectangle frame. This would make the frame resize and refill on orientation as well.
This is merely an example code for reference.
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}
I made a a custom keyboard toolBar:
To do that I created a toolbar
let keyboardToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 44))
and a view for the banner
adToolbar = GADBannerView(frame: CGRectMake(0, 44, self.view.bounds.size.width, 44))
then I grouped them in another UIToolbar (I tried UIView too)
let clusterView = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 88))
clusterView.addSubview(adToolbar)
clusterView.addSubview(keyboardToolbar)
and I added the view to the UITextField's keyboard.
Everything ok, but when I rotate the device happens this:
(clusterView UIToolbar resize correctly but not the two contained bars...)
I tried with
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
methodWhichGeneratesToolbar()
}
But it's the same, what can I try to solve this issue?
P.S.: I've made an example project.
Here's a suggestion on how to do it with programmatic autolayout using Visual Format Language (VFL). You'll need to have a look at the VFL docs to understand the VFL string syntax (They are pinning the outer view to the top and sides of the main view, and pinning the two subviews inside and to each other, and setting their height to 44).
I don't have AdMob installed, so I used a regular UIView instead of the banner view, but hopefully it should resize similarly - this code works ok on the 9.2 simulator in a test app
let keyboardToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 44))
keyboardToolbar.translatesAutoresizingMaskIntoConstraints = false //This is critical for all programmatic autolayout - if you forget it nothing will work
let adToolbar = UIView(frame: CGRectMake(0, 44, self.view.bounds.size.width, 44))
adToolbar.translatesAutoresizingMaskIntoConstraints = false
let clusterView = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 88))
clusterView.translatesAutoresizingMaskIntoConstraints = false
//Map views to keys used in visual format language strings
let views = ["keyboardToolbar":keyboardToolbar,"adToolbar":adToolbar,"clusterView":clusterView]
//Map values to strings used in vfl strings
let metrics = ["barHeight":44]
//In named variables to make it clear what they are
//Syntax is explained in link above
let verticalConstraintsStr = "V:|[keyboardToolbar(barHeight)][adToolbar(barHeight)]|"
let adHorizontalConstraintsStr = "|[adToolbar]|"
let keyboardHorizontalConstraintsStr = "|[keyboardToolbar]|"
let subViewConstraintStrs = [
verticalConstraintsStr,
adHorizontalConstraintsStr,
keyboardHorizontalConstraintsStr
]
//Views must be added to subviews before adding constraints
// if the superview is referenced using
//the | symbol in the VFL strings
clusterView.addSubview(keyboardToolbar)
clusterView.addSubview(adToolbar)
//Converts strings to constraints for subviews and add them
for constraintStr in subViewConstraintStrs {
let allConstraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintStr, options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views)
clusterView.addConstraints(allConstraints)
}
let clusterVerticalConstraintsStr = "V:|[clusterView]" //Note no | at the end - no bottom pin
let clusterHorizontalConstraintsStr = "|[clusterView]|"
view.addSubview(clusterView)
//Same process for the enclosing view
for constraintStr in [clusterVerticalConstraintsStr,clusterHorizontalConstraintsStr] {
let allConstraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintStr, options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views)
view.addConstraints(allConstraints)
}
VFL is powerful but annoying to debug, and can't do all types of constraint (e.g. not alignments - you have to use an even more verbose API for those).