iOS - UIView of same small width does not layout on smaller devices - ios

I have an UIView that is defined below -
private let verticalSeparatorLine: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alpha = 0.94
view.backgroundColor = .lightGray
return view
}()
This is how I add it to the viewcontroller's view.
override func viewDidLoad() {
super.viewDidLoad()
addTestView()
}
private func addTestView() {
view.addSubview(verticalSeparatorLine)
NSLayoutConstraint.activate([
verticalSeparatorLine.centerX.constraint(to: view.centerX),
verticalSeparatorLine.bottomAnchor.constraint(to: view.bottomAnchor),
verticalSeparatorLine.widthAnchor.constraint(0.3),
verticalSeparatorLine.heightAnchor.constraint(70)
])
}
If I add this verticalSeparatorLine to the ViewController's view in a larger device like iPhone X, the view lays out as expected. In smaller devices the view's width is zero. If I increase the width to say 2, it appears on both the devices.
Am I missing something?
Update:
If I replace with leadingAnchor, verticalSeparatorLine is visible with a width of 0.5.
verticalSeparatorLine.leadingAnchor.constraint(to: view.leadingAnchor, constant: 10),

It depends on the device's resolution, if UIScreen.main.scale == 2 means you set width 1 equals to 2 pixel's width, so 0.3(0.6pixel) can't be displayed, but in scale3 device, 0.33 just equals to 1 pixel.

Related

Dynamically Sized View based on Child View Controller

I have a custom view controller used as a child view controller:
class ChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
calculatePreferredSize()
}
func calculatePreferredSize() {
let targetSize = CGSize(width: view.bounds.width,
height: UIView.layoutFittingCompressedSize.height)
preferredContentSize = view.systemLayoutSizeFitting(targetSize)
}
}
then in the main view controller, I have this code:
class ViewController: UIViewController {
var container : UIView!
var childVC : ChildViewController!
var containerHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .purple
// setup container to hold child vc
container = UIView()
container.backgroundColor = .systemPink
container.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(container)
container.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
container.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
containerHeightConstraint = NSLayoutConstraint()
containerHeightConstraint = container.heightAnchor.constraint(equalToConstant: 0)
containerHeightConstraint.isActive = true
// setup child vc
childVC = ChildViewController()
addChild(childVC)
container.addSubview(childVC.view)
childVC.view.frame = container.bounds
childVC.didMove(toParent: self)
// add contents into the child vc
let newView = UIView()
childVC.view.addSubview(newView)
newView.backgroundColor = .systemBlue
newView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
newView.topAnchor.constraint(equalTo: newView.superview!.topAnchor),
newView.leadingAnchor.constraint(equalTo: newView.superview!.leadingAnchor),
newView.trailingAnchor.constraint(equalTo: newView.superview!.trailingAnchor),
newView.heightAnchor.constraint(equalToConstant: 123),
])
}
override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
super.preferredContentSizeDidChange(forChildContentContainer: container)
if (container as? ChildViewController) != nil {
containerHeightConstraint.constant = container.preferredContentSize.height
}
}
}
I am trying to dynamically size the container view in the main VC based on the child's calculated height. The preferredContentSizeDidChange method is being called but in my calculation of the child VC's height (using UIView.layoutFittingCompressedSize), I'm always getting back 0. Even though I've checked the frame of the view added onto that view and it has the correct frame height (in this example, 123). As shown in the output logging below:
(lldb) po view.subviews
▿ 1 element
- 0 : <UIView: 0x12251cd40; frame = (0 0; 350 123); layer = <CALayer: 0x6000007a0e60>>
(lldb) po UIView.layoutFittingCompressedSize
▿ (0.0, 0.0)
- width : 0.0
- height : 0.0
Below is a screenshot from the simulator.
Am I using the UIView.layoutFittingCompressedSize incorrectly? How do I calculate the height of the child view based on its contents?
Autolayout can't calculate the newView content height, because it is missing constraints in the Y axis to solve the equation.
newView has only these constraints defined: top, leading, trailing and height.
It is missing the bottom constraint:
newView.bottomAnchor.constraint(equalTo: newView.superview!.bottomAnchor).isActive = true
The full set of constraints would look like the following:
NSLayoutConstraint.activate([
newView.topAnchor.constraint(equalTo: newView.superview!.topAnchor),
newView.leadingAnchor.constraint(equalTo: newView.superview!.leadingAnchor),
newView.trailingAnchor.constraint(equalTo: newView.superview!.trailingAnchor),
newView.heightAnchor.constraint(equalToConstant: 123),
newView.bottomAnchor.constraint(equalTo: newView.superview!.bottomAnchor)
])
Afterwards when I place a breakpoint into preferredContentSizeDidChange, I can print the container.preferredContentSize.height, which is 123.0.
EDIT
To avoid constraint breakage, we also need to use autolayout for childVC.view. Right now it is using autosizing mask, which only flows from top-down and creates constraints with 1000 priority.
childVC.view.frame = container.bounds
needs to be replaced with
childVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
childVC.view.topAnchor.constraint(equalTo: container.topAnchor),
childVC.view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
childVC.view.trailingAnchor.constraint(equalTo: container.trailingAnchor),
childVC.view.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])
and the containerHeightConstraint needs to have a reduced priority for the 0 height constraint, otherwise the system will always find the constraints ambiguous - the child controller wants to be 123 points tall, but the container height constraint is still at 0 before we call the preferredContentSizeDidChange method.
containerHeightConstraint.priority = .defaultLow

Dynamically find the right zoom scale to fit portion of view

I have a grid view, it's like a chess board. The hierarchy is this :
UIScrollView
-- UIView
---- [UIViews]
Here's a screenshot.
Knowing that a tile has width and height of tileSide, how can I find a way to programmatically zoom in focusing on the area with the blue border? I need basically to find the right zoomScale.
What I'm doing is this :
let centralTilesTotalWidth = tileSide * 5
zoomScale = CGFloat(centralTilesTotalWidth) / CGFloat(actualGridWidth) + 1.0
where actualGridWidth is defined as tileSide multiplied by the number of columns. What I'm obtaining is to see almost seven tiles, not the five I want to see.
Keep also present that the contentView (the brown one) has a full screen frame, like the scroll view in which it's contained.
You can do this with zoom(to rect: CGRect, animated: Bool) (Apple docs).
Get the frames of the top-left and bottom-right tiles
convert then to contentView coordinates
union the two rects
call zoom(to:...)
Here is a complete example - all via code, no #IBOutlet or #IBAction connections - so just create a new view controller and assign its custom class to GridZoomViewController:
class GridZoomViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
let contentView: UIView = {
let v = UIView()
return v
}()
let gridStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.distribution = .fillEqually
return v
}()
var selectedTiles: [TileView] = [TileView]()
override func viewDidLoad() {
super.viewDidLoad()
[gridStack, contentView, scrollView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
var bColor: Bool = false
// create a 9x7 grid of tile views, alternating cyan and yellow
for _ in 1...7 {
// horizontal stack view
let rowStack = UIStackView()
rowStack.translatesAutoresizingMaskIntoConstraints = false
rowStack.axis = .horizontal
rowStack.distribution = .fillEqually
for _ in 1...9 {
// create a tile view
let v = TileView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = bColor ? .cyan : .yellow
v.origColor = v.backgroundColor!
bColor.toggle()
// add a tap gesture recognizer to each tile view
let g = UITapGestureRecognizer(target: self, action: #selector(self.tileTapped(_:)))
v.addGestureRecognizer(g)
// add it to the row stack view
rowStack.addArrangedSubview(v)
}
// add row stack view to grid stack view
gridStack.addArrangedSubview(rowStack)
}
// add subviews
contentView.addSubview(gridStack)
scrollView.addSubview(contentView)
view.addSubview(scrollView)
let padding: CGFloat = 20.0
// respect safe area
let g = view.safeAreaLayoutGuide
// for scroll view content constraints
let cg = scrollView.contentLayoutGuide
// let grid width shrink if 7:9 ratio is too tall for view
let wAnchor = gridStack.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0)
wAnchor.priority = .defaultHigh
NSLayoutConstraint.activate([
// constrain scroll view to view (safe area), all 4 sides with "padding"
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: padding),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: padding),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -padding),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -padding),
// constrain content view to scroll view contentLayoutGuide, all 4 sides
contentView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
// content view width and height equal to scroll view width and height
contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: 0.0),
contentView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor, constant: 0.0),
// activate gridStack width anchor
wAnchor,
// gridStack height = gridStack width at 7:9 ration (7 rows, 9 columns)
gridStack.heightAnchor.constraint(equalTo: gridStack.widthAnchor, multiplier: 7.0 / 9.0),
// make sure gridStack height is less than or equal to content view height
gridStack.heightAnchor.constraint(lessThanOrEqualTo: contentView.heightAnchor),
// center gridStack in contentView
gridStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0.0),
gridStack.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0.0),
])
// so we can see the frames
view.backgroundColor = .blue
scrollView.backgroundColor = .orange
contentView.backgroundColor = .brown
// delegate and min/max zoom scales
scrollView.delegate = self
scrollView.minimumZoomScale = 0.25
scrollView.maximumZoomScale = 5.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return contentView
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil, completion: {
_ in
if self.selectedTiles.count == 2 {
// re-zoom the content on size change (such as device rotation)
self.zoomToSelected()
}
})
}
#objc
func tileTapped(_ gesture: UITapGestureRecognizer) -> Void {
// make sure it was a Tile View that sent the tap gesture
guard let tile = gesture.view as? TileView else { return }
if selectedTiles.count == 2 {
// if we already have 2 selected tiles, reset everything
reset()
} else {
// add this tile to selectedTiles
selectedTiles.append(tile)
// if it's the first one, green background, if it's the second one, red background
tile.backgroundColor = selectedTiles.count == 1 ? UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0) : .red
// if it's the second one, zoom
if selectedTiles.count == 2 {
zoomToSelected()
}
}
}
func zoomToSelected() -> Void {
// get the stack views holding tile[0] and tile[1]
guard let sv1 = selectedTiles[0].superview,
let sv2 = selectedTiles[1].superview else {
fatalError("problem getting superviews! (this shouldn't happen)")
}
// convert tile view frames to content view coordinates
let r1 = sv1.convert(selectedTiles[0].frame, to: contentView)
let r2 = sv2.convert(selectedTiles[1].frame, to: contentView)
// union the two frames to get one larger rect
let targetRect = r1.union(r2)
// zoom to that rect
scrollView.zoom(to: targetRect, animated: true)
}
func reset() -> Void {
// reset the tile views to their original colors
selectedTiles.forEach {
$0.backgroundColor = $0.origColor
}
// clear the selected tiles array
selectedTiles.removeAll()
// zoom back to full grid
scrollView.zoom(to: scrollView.bounds, animated: true)
}
}
class TileView: UIView {
var origColor: UIColor = .white
}
It will look like this to start:
The first "tile" you tap will turn green:
When you tap a second tile, it will turn red and we'll zoom in to that rectangle:
Tapping a third time will reset to starting grid.

Autolayout programmatic aspect ratio setting

I am trying to add a subview to view and define autolayout constraints, including aspect ratio. But aspect ratio that I see at runtime is not what I defined in constraints. What am I doing wrong? As you can see in code, background view height should be 0.5 of background view width, but that's not the case here in the screenshot. Here is my code:
class ViewController: UIViewController {
private var backgroundView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.orange
backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
backgroundView?.backgroundColor = UIColor.black.withAlphaComponent(1.0)
backgroundView?.layer.borderColor = UIColor.white.cgColor
backgroundView?.layer.borderWidth = 1.5
backgroundView?.layer.cornerRadius = 4
backgroundView?.clipsToBounds = true
backgroundView?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(backgroundView!)
backgroundView?.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
backgroundView?.heightAnchor.constraint(equalTo: backgroundView!.widthAnchor, multiplier: 0.5).isActive = true
backgroundView?.centerXAnchor.constraint(equalTo: view!.centerXAnchor, constant: 0).isActive = true
backgroundView?.topAnchor.constraint(equalTo: view!.topAnchor, constant: 4).isActive = true
}
}
Here is the screenshot:
"background view height should be 0.5 of background view width"
Your screenshot size is 1334 x 750
Your backgroundView - including the border - is 1334 x 667
1334 * 0.5 == 667
So, you are getting exactly what you are asking for.
Try to change height constraint to set:
backgroundView?.heightAnchor.constraint(equalTo: view!.heightAnchor, multiplier: 0.5).isActive = true
NB: You are getting the exact result which you are looking for. It has the height that's half of its width. There is nothing wrong with the screenshot.

Dynamically size UIStackView to the width of the UIViews inside

I am trying to create a UIStackView with three UIViews inside. The UIViews will have a circle with text over / in it.
I would like not to set the StackView to a static number, i would like it to be able to get smaller/grow based on the device the user is using.
Right now, the StackView is being added to the view, and the UIViews are being added to that. The colors are being displayed, but the rounded circles are not and the StackView height is not equal to the leftui's width.
Basically, I need three circles of equal height and width....is there a better way for this?
Here is my code.
#IBOutlet var stack: UIStackView!
override func viewWillLayoutSubviews() {
//let stack = UIStackView()
let leftui = UIView()
let middleui = UIView()
let rightui = UIView()
stack.addArrangedSubview(leftui)
stack.addArrangedSubview(middleui)
stack.addArrangedSubview(rightui)
leftui.backgroundColor = UIColor.red
middleui.backgroundColor = UIColor.blue
rightui.backgroundColor = UIColor.brown
leftui.bounds.size.height = leftui.bounds.width //needs these to new equal
middleui.bounds.size.height = middleui.bounds.width //needs these to new equal
rightui.bounds.size.height = rightui.bounds.width //needs these to new equal
leftui.layer.cornerRadius = leftui.bounds.size.width / 2
middleui.layer.cornerRadius = middleui.bounds.size.width / 2
rightui.layer.cornerRadius = rightui.bounds.size.width / 2
print(leftui.bounds.size.width) //prints 0.0
leftui.clipsToBounds = true
middleui.clipsToBounds = true
rightui.clipsToBounds = true
stack.sizeToFit()
stack.layoutIfNeeded()
view.addSubview(stack)
}
Here is what I was looking for.
This is from the android version of the application.
I think that in order for UIStackView to work its arrangedSubviews have to use autolayout - Check first answer here: Is it necessary to use autolayout to use stackview
This is how you could solve this:
Add a new class for your circular views, these do not do much other than set its layer.cornerRadius to half of their width, so that if height and width are the same they will be circular.
class CircularView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = true
layer.cornerRadius = bounds.midX
}
}
You add a widthConstraint with which you will be able to size the elements in the stack view
var widthConstraint: NSLayoutConstraint!
You can then create the UIStackView, I used your code mostly to do this:
override func viewDidLoad() {
super.viewDidLoad()
let leftui = CircularView()
let middleui = CircularView()
let rightui = CircularView()
leftui.translatesAutoresizingMaskIntoConstraints = false
middleui.translatesAutoresizingMaskIntoConstraints = false
rightui.translatesAutoresizingMaskIntoConstraints = false
leftui.backgroundColor = UIColor.red
middleui.backgroundColor = UIColor.blue
rightui.backgroundColor = UIColor.brown
let stack = UIStackView(arrangedSubviews: [leftui, middleui, rightui])
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
widthConstraint = leftui.widthAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([
widthConstraint,
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
leftui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
middleui.widthAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
middleui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
rightui.widthAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0),
rightui.heightAnchor.constraint(equalTo: leftui.widthAnchor, multiplier: 1.0)
])
}
Given the constraints set here, circles will have a width/height of 100 and stack view is centred in the view.
Next if you want to do something when view rotates you could implement something like this in your viewController
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: { _ in
if size.width > size.height {
self.widthConstraint.constant = 150
} else {
self.widthConstraint.constant = 100
}
}, completion: nil)
}
It would animate to circles of width/height of 150 in landscape. You can then play with these values to get desired outcome.
To design this, follow the below steps.
create a custom view. in the custom view put all the subviews like
cost title label, price label and the color UIImageView
Now create three object of the custom view with proper data.
Get the device screen width divide by 3 gives each custom view
width, also set the view height as per your requirement and provide
frame for the created custom view
Now add the three views to the StackView.
Hope this will help to design, if you need any more help please comment.

Center UIImageView within a UIScrollView with a larger contentSize

I have a full screen scrollView, to which I add an imageView as subview. I want the imageView to be centered and scaled filling the scrollView's size (that is the screen size) at the beginning, but then to allow the user to scroll the image in both directions (vertical and horizontal) with equal offsets at left, right, top and bottom.
I mean: I've set the scroll view's contentSize to be CGSize(width: screenWidth + 200, height: screenHeight + 200), and if I run the app, I see that I am able to scroll those 200 pts of offset only to the right and to the bottom of the image. I'd like the image to be centered in the content size, and to be able to scroll it horizontally to both to the left and to the right with offset 100 pts each side (similar thing with top and bottom when scrolling vertically).
How could I achieve this?
Note: I'm setting all the UI in code, I'm not using storyboards nor xib files
You may find it easier / more intuitive to use constraints and auto-layout rather than screenWidth and screenHeight:
//
// CenteredScrollViewController.swift
// SW4Temp
//
// Created by Don Mag on 4/18/18.
//
import UIKit
class CenteredScrollViewController: UIViewController {
let theScrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.green
return v
}()
let theImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.blue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scrollView to the main view
view.addSubview(theScrollView)
// add the imageView to the scrollView
theScrollView.addSubview(theImageView)
// pin the scrollView to all four sides
theScrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
theScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true
theScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
theScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
// constrain the imageView's width and height to the scrollView's width and height
theImageView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, multiplier: 1.0).isActive = true
theImageView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, multiplier: 1.0).isActive = true
// set the imageView's top / bottom / leading / trailing anchors
// this *also* determines the scrollView's contentSize (scrollable area)
// with 100-pt padding on each side
theImageView.topAnchor.constraint(equalTo: theScrollView.topAnchor, constant: 100.0).isActive = true
theImageView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor, constant: -100.0).isActive = true
theImageView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor, constant: 100.0).isActive = true
theImageView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor, constant: -100.0).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// set the scrollView's contentOffset (to center the imageView)
theScrollView.contentOffset = CGPoint(x: 100, y: 100)
}
}
You can move only down and right because your current content offset is 0,0 so top left - thus you can move down 200 and right 200.
What you want is to be scrolled 1/2 of vertical padding and 1/2 of horizontal padding, so in your case you would do scrollView.contentOffset = CGPoint(x: 100, y: 100)
Also for everything to work, UIImageView has to be same size as scrollView's contentSize, so bigger than screen size.
Given the comments what I think you want is the image to fill the screen and then user could scroll outside of bounds of the image, then you just need to make UIImageView's size be size of the screen its x and y coordinates to be same as contentOffset of the scrollView so (100, 100).
Here is the video of the sample app doing this:
https://dzwonsemrish7.cloudfront.net/items/2v361r2p0O2j1D3x3W10/Screen%20Recording%202018-04-19%20at%2002.32%20PM.mov
try this in
Swift 4.* or 5.*
let maxScale = self.imageScrollView.maximumZoomScale
let minScale = self.imageScrollView.minimumZoomScale
if let imageSize = imageView.image?.size{
let topOffset: CGFloat = (boundsSize.height - minScale * imageSize.height ) / 2
let leftOffset: CGFloat = (boundsSize.width - minScale * imageSize.width ) / 2
self.imageScrollView.contentInset = UIEdgeInsets(top: topOffset, left: leftOffset, bottom: 0, right: 0)
}

Resources