I have UICollectionView that contains cells with large images. Scrolling was smooth until I added (small) UIVisualEffectView to each cell. Now, scrolling performance is awful.
Here is all the code that does something with that UIVisualEffectView the code:
class ThemeCardCell: UICollectionViewCell {
private let priceTagEffectView = UIVisualEffectView()
override func layoutSubviews() {
super.layoutSubviews()
if priceTagEffectView.superview == nil {
priceTagEffectView.effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
priceTagEffectView.frame = CGRect(x: bounds.width - priceTagMargin.width - 80, y: priceTagMargin.height, width: 80, height: 40)
priceTagEffectView.opaque = true
addSubview(priceTagEffectView)
}
}
}
What can I do to improve scrolling performance?
Don't use UIVisualEffectView in this way, that's what you can do. Apple has given lots of info about animation / scrolling performance and that sort of thing is at the top of the list of what not to do. And visual effect views are the worst; they don't merely blur an image - they are performed at a much higher point in the render chain, and you are forcing them to re-render on every frame (hence the problem).
Related
I am trying to create custom table view cell which works fine in my other UIViewControllers. However, in one of my controllers, the shadow is not growing, I can barely see the shadow.
Here is an image of the shadow being shown in red, you can see it is barely visible.
My cell has a UIView added inside the contentView to creating floating cell effects - the same code and same storyboard layouts are being used across my controllers but this is the only table view where the shadow issue is occurring - so I must be missing something.
My addShadow extension:
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) {
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
}
}
My awakeFromNib on the custom cell:
:: cellContentView is my UIView added to the base contentView of the cell.
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
self.selectionStyle = .none
cellContentView?.layer.masksToBounds = true
cellContentView?.round(corners: [.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 10)
cellContentView?.addShadow(offset: CGSize(width: 40, height: 60), color: UIColor.red, radius: 10, opacity: 1)
cellContentView?.layer.shouldRasterize = true
}
Note: The .round is an extension being used on all my cells.
No matter what radius or offset I add for this shadow, it does not get bigger than the image. Also, none of my other cells in the their controllers require the shouldRasterize property to be set, but this does.
Does anyone know what is happening here?
Thanks :)
Edit
Strangely, if I add constraints around my view to keep the gaps large between my view and the cell content view, the background colour disappears - this is set to white in the storyboard.
You should call in the layoutSubviews method. because shadow should add after the view is uploaded
override func awakeFromNib() {
super.awakeFromNib()
//init methods
}
override public func layoutSubviews() {
super.layoutSubviews()
//Added shadow
self.reloadLayers()
}
private func reloadLayers() {
self.layer.cornerRadius = 5
self.addShadow(.TransactionCell)
}
I hope it helps
Content view will fill you cell, so you need to add shadow to view inside content view which has all your components inside it. Then add constraints to it with gap between that view and content view. Second, 40 and 60 properties for shadow is likely too large, when I said too large I mean unbelievable large, because gap between content views in cells are no more than 15 - 30 even less. so try it with much less values, while radius can remain 10 but you will see what value fit the best. If cell content view is your custom view just values will did the job if your view is not kind of transparent or any inside it, in that case it won't, and there is hard to fix that, I tried many libraries and custom codes and it is never ok.
squircleView.layer.cornerRadius = 40
squircleView.layer.cornerCurve = CALayerCornerCurve.continuous
squircleView.layer.shadowColor = UIColor.systemGray.cgColor
squircleView.layer.shadowOpacity = 0.7
squircleView.layer.shadowOffset = CGSize(width: 0, height: 0.5)
squircleView.layer.shadowRadius = 5
I have two (possibly more) views in a UIScrollView and want to use paging with it. The problem arises when I try to use the default Paging option for UIScrollView, since the views have different widths it can not page properly.
So I have implemented a custom paging code which works. However, when the scrolls are slow, it does not function as expected. (It goes back to the original position without animation.)
Here is how I currently do the custom paging through the UIScrollViewDelegate
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if direction == 1{
targetContentOffset.pointee.x = 0
}else{
targetContentOffset.pointee.x = 100
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
direction = 1
}
else {
direction = 0
}
}
What I want:
What I have:
try to below example for Custom UIScrollView Class
import UIKit
public class BaseScrollViewController: UIViewController, UIScrollViewDelegate {
public var leftVc: UIViewController!
public var middleVc: UIViewController!
public var rightVc: UIViewController!
public var initialContentOffset = CGPoint() // scrollView initial offset
public var maximumWidthFirstView : CGFloat = 0
public var scrollView: UIScrollView!
public class func containerViewWith(_ leftVC: UIViewController,
middleVC: UIViewController,
rightVC: UIViewController) -> BaseScrollViewViewController {
let container = BaseScrollViewViewController()
container.leftVc = leftVC
container.middleVc = middleVC
container.rightVc = rightVC
return container
}
override public func viewDidLoad() {
super.viewDidLoad()
setupHorizontalScrollView()
}
func setupHorizontalScrollView() {
scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
scrollView.frame = CGRect(x: view.x,
y: view.y,
width: view.width,
height: view.height
)
self.view.addSubview(scrollView)
let scrollWidth = 3 * view.width
let scrollHeight = view.height
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
leftVc.view.frame = CGRect(x: 0,
y: 0,
width: view.width,
height: view.height
)
middleVc.view.frame = CGRect(x: view.width,
y: 0,
width: view.width,
height: view.height
)
rightVc.view.frame = CGRect(x: 2 * view.width,
y: 0,
width: view.width,
height: view.height
)
addChildViewController(leftVc)
addChildViewController(middleVc)
addChildViewController(rightVc)
scrollView.addSubview(leftVc.view)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(rightVc.view)
leftVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
rightVc.didMove(toParentViewController: self)
scrollView.contentOffset.x = middleVc.view.frame.origin.x
scrollView.delegate = self
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.initialContentOffset = scrollView.contentOffset
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if maximumWidthFirstView != 0
{
if scrollView.contentOffset.x < maximumWidthFirstView
{
scrollView.isScrollEnabled = false
let newOffset = CGPoint(x: maximumWidthFirstView, y: self.initialContentOffset.y)
self.scrollView!.setContentOffset(newOffset, animated: false)
scrollView.isScrollEnabled = true
}
}
}
}
Use of BaseScrollViewController
let left = FirstController.init()
let middle = MiddleController()
let right = RightController.init()
let container = BaseScrollViewController.containerViewWith(left,middleVC: middle,rightVC: right)
container.maximumWidthFirstView = 150
Output:
GitHub gist Example code: https://gist.github.com/mspvirajpatel/58dac2fae0d3b4077a0cb6122def6570
I have previously written a short memo about this problem, and I'll copy/paste it since it is no longer accessible from anywhere. This may not be a specific answer and the codes are pretty old, but I hope this would help you in some degree.
If you have used a paging feature included in UIScrollView, you might also have tempted to customize the width of each page instead of a default, boring, frame width paging. It would be great if you can make the scroll stop at shorter or longer intervals than just multiples of its frame width. Surprisingly, there's no built-in way to configure the width of pages even in our latest iOS7 SDK. There are some ways to achieve custom paging, but none of them I would say are complete. As for now, you'll have to choose either of the following solutions.
1. Change the frame size of your UIScrollView
Alexander Repty has introduced a nice and easy solution to this problem and also included a sample code through his blog: http://blog.proculo.de/archives/180-Paging-enabled-UIScrollView-With-Previews.html
Basically, the instruction can be watered down to the following steps:
Create UIView subclass and override hitTest: withEvent:.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
if ([self.subviews count] == 0) return nil;
else return [self.subviews lastObject];
}
return nil;
}
Include UIScrollView as a subview of the above UIView subclass.
Adjust the frame size of your UIScrollView.
Set clipsToBound property of your scroll view to NO.
Set pagingEnabled property of your scroll view to YES.
As you can see, I've just assumed that there is only one subview (the scrollView!) to your UIView subclass. Since you are passing all the touch events occurred in the UIView subclass to your UIScrollView, you'll be able to scroll the content by panning on the UIView subclass, but the paging width will be decided by the width of UIScrollView's frame.
The best part of this approach is that you'll get the genuine feeling and responsiveness, as it is somewhat hard to mimic the paging by using UIScrollView delegate methods.
The only problem I found using this solution is that the width of all pages will have to be identical. You can't set different widths to different pages. If you tries to change your scrollView's frame size dynamically, you'll find there're a number of new emerging problems to deal with. Before trying to fix these glitches, you may want to check out other two solutions using UIScrollView delegates.
2. scrollViewWillEndDragging: withVelocity: targetContentOffset
scrollViewWillEndDragging: withVelocity: targetContentOffset is one of the latest UIScrollView delegate methods(iOS 5.0 or up) that gives you more information than the other old ones.
Since you get the velocity of the scrollView right after you lift the finger up from the screen, we can figure out the direction of the scrolled contents. The last argument, targetContentOffset, not only gives you the expected offset when the scrolling stops eventually, you can also assign CGPoint value in order to let the scrollView scrolls to the desired point.
targetContentOffset = CGPointMake(500, 0);
or
targetContentOffset->x = 500;
However, this will not work as you would think it should because you cannot set the speed of scrolling animation. It feels more like the scrollView happens to stop at the right point rather than it snaps to the spot. I also have to warn you that manually scrolling the contents with setContentOffset: animated: or just by using UIView animation inside the method will not work as expected.
If the velocity is 0, however, you may(and you have to) use manual scrolling to make it snap to the nearest paging point.
It could be the simplest and the most clean approach among all, but the major downside is that it does not provide the same experience that you always had with the real paging feature. To be more honest, it's not even similar to what we call paging. For the better result, we need to combine more delegate methods.
3. Use multiple UIScrollView delegate methods
From my shallow experience, an attempt to scroll your scrollView manually inside any UIScrollView delegate methods will only work when your scrollView has started to decelerate, or when it's not scrolling at all. Therefore, the best place I've found to perform the manual scrolling is scrollViewWillBeginDecelerating:.
Before looking inside the sample code, remember scrollViewEndDragging: withVelocity: targetContentOffset: method will always called prior to scrollViewWillBeginDecelerating:.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
_scrollVelocity = velocity.x;
if (_scrollVelocity == 0) {
// Find the nearest paging point and scroll.
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if (_scrollVelocity < 0) {
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
scrollView.contentOffset = // Previous page offset
} completion:^(BOOL finished){}];
} else if (_scrollVelocity > 0) {
// Animate to the next page offset
}
}
_scrollVelocity is meant to be a global variable or a property, and I've assumed that you have your own ways to decide paging offsets for each page. Note that you'll have to handle the case of zero velocity inside the upper method because the latter method will not be called.
UIView animation with the duration 0.3 and the EaseOut curve option gave me the best result, but of course you should try other combinations to find what's the best for you.
This not the exact solution you might be looking for.
1) Check the offset of the scrollView when it reaches 0, You could show the VIEW you have above , You could animate while checking the scrollview movement so that it looks nice .But not completely
2) Now the VIEW is partially above your camera(you can decrease it alpha so that scrollview is still visible).
3) user can tap the view and you can show it completely.
You may want to consider calculating the most visible cell in your collection view after dragging ends and then programmatically scroll to – and center – that cell.
So something like:
First, implement the scrollViewDidEndDragging(_:willDecelerate:) method of your collection view's delegate. Then, in that method's body, determine which cell in collectionView.visibleCells is most visible by comparing each of their centers against your collection view's center. Once you find your collection view's most visible cell, scroll to it by calling scrollToItem(at:at:animated:).
I am trying to create a cover flow image slide something like this :
I have to say I don't want to use others framework like iCarousel I need to write my own code. here is my codes but it only shows me one image per page , I was wondering how can I change my code to add previous and next image like the image ?
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = imageArray.count
scrollView.frame = self.view.frame
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(pageControl.numberOfPages) , height: 0)
scrollView.delegate = self
for i in 0..<imageArray.count {
ashvanImage = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 50 , height: self.view.frame.height - 200))
ashvanImage.center = scrollView.center
ashvanImage.contentMode = .scaleAspectFit
ashvanImage.image = UIImage(named: "\(i).jpg")
scrollView.addSubview(ashvanImage)
createPageWith(images: ashvanImage, page: i)
}
}
func createPageWith(images:UIImageView, page:Int) {
let newView = UIView(frame: CGRect(x: scrollView.frame.size.width * CGFloat(page), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height))
newView.addSubview(images)
scrollView.addSubview(newView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let page = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(page)
}
As others have said, if you don't want to use iCarousel, which has CoverFlow built in, you should use a UICollectionView. It is built for what you want.
Certainly you could do this starting from a UIScrollView, but it would probably take an experienced developer several weeks of full-time work to get a clean design working and debugged, and the result would look and feel a whole lot like a collection view when you were done, only not as flexible or as maintainable. Plus you'd need pretty advanced knowledge of Core Animation, which is pretty specialized and not very well documented.
EDIT:
A Google search on "UICollectionView CoverFlow Swift" found this framework on Github:
https://github.com/sumitlni/LNICoverFlowLayout
If you're determined to do this yourself, you could at least look over that framework as a road map for what you'd need to do.
You are set scrollview's width equal to the whole view, make it less like 85% or 90% or according to your requirement.
Which will solve half of your problem.
By looking at the design i think you want the view to animate.
So first, turn off scrolling for the scrollview.
Then add 2 gesture's on the scrollview, one for right swipe and another for left swipe.
Then scroll the view 1 page at a time according to the gesture and animate the view's size by changing its x position by 1 page and decreasing its size and increasing the incoming one's.
You can use the default method UIScrollView.animate too.
So I am using a UITableView to display information about different films.
At the top of the VC, I have a UIImage which sits inside of a UIView. And then my table sits underneath. The table currently sits right up against the bottom of the image (which is what I want), see below:
The Issue
I followed a tutorial to add a simple effect, so when the user pulls down on the tableView, the image enlarges. You can see what I mean by seeing the tutorial here: See here
This all worked wonderful and gave my the effect I wanted, however, it's now added an empty space below the image, see the image below:
Everything still works fine, and the effect works as expected, but this space is now there - which I really don't want.
The settings in the storyboard for this VC are set as followed:
The code I added to make the effect is as follows:
private let KTableHeaderHeight: CGFloat = 160.0 // which is the height of my UIImage
var headerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
headerView = tableView.tableHeaderView
tableView.tableHeaderView = nil
tableView.addSubview(headerView)
tableView.contentInset = UIEdgeInsets(top: KTableHeaderHeight, left: 0, bottom: 0, right: 0)
tableView.contentOffset = CGPoint(x: 0, y: -KTableHeaderHeight)
updateHeaderView()
}
And then:
func updateHeaderView() {
var headerRect = CGRect(x: 0, y: -KTableHeaderHeight, width: tableView.bounds.width, height: KTableHeaderHeight)
if tableView.contentOffset.y < -KTableHeaderHeight {
headerRect.origin.y = tableView.contentOffset.y
headerRect.size.height = -tableView.contentOffset.y
}
headerView.frame = headerRect
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
updateHeaderView()
}
If I comment out all the code I've added, it then looks fine again, so I'm guessing it is this code that's causing the space.
I'm really keen to understand why this gap has formed, and how I can remove it, still using the code added to make the image enlarging effect.
Update
My UIImageView layout Attributes:
Your issue is likely the private let KTableHeaderHeight: CGFloat = 160.0 line, which doesn't equal the height of the imageView in the header.
You need to find out the fixed height of the imageView after scaling, which you can get by multiplying the original image height by view width/original image width, and set the imageView height and private var KTableHeaderHeight: CGFloat to that value.
If your using a grouped UITableView then it makes a space for the group, not sure how to get rid of it. You might want to consider making it a plan UITableView and then create the sections headers that you want via the delegate
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// create UIView here
}
However, after playing with the sample project, it seems like you can adjust your kTableHeightHeader variable in the top to adjust to hide the top section header.
I modified the sample project so you can see it here
Swift code:
tableView.tableFooterView = UIView()
Objective C code:
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
I would like to achieve this result:
Searching around I found out that probably the way to do it is using UICollectionView, so no problem with that since there are many tutorials and questions on Stack Overflow. I have 3 questions:
I cannot find anything about the "separators" (the line that divides all the boxes). I like that it doesn't touch the screen borders horizontally. Is it done programmatically?
To divide the space equally in all devices (3 boxes/buttons horizontally) I found this answer answer. Is this the right approach?
For the Blur effect I found this answer: How to implement UIVisualEffectView in UITableView with adaptive segues
For a TableView it would be:
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
tableView.backgroundColor = UIColor.clearColor()
let blurEffect = UIBlurEffect(style: .Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
tableView.backgroundView = blurEffectView
}
Can I do something like this?
#IBOutlet var collectionView: UICollectionView!
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
collectionView.backgroundColor = UIColor.clearColor()
let blurEffect = UIBlurEffect(style: .Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
collectionView.backgroundView = blurEffectView
}
Create the UICollectionViewController like this in a file that sub-classes from UICollectionViewController:
convenience override init() {
var layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSizeMake(<width>, <height>)
// Setting the space between cells
layout.minimumInteritemSpacing = <Space between columns>
layout.minimumLineSpacing = <Space between rows>
return (self.init(collectionViewLayout: layout))
}
In the viewDidLoad you an set the background color like this:
self.collectionView.backgroundColor = UIColor.orangeColor()
My guess is you can set a background image like this:
self.collectionView?.backgroundColor = UIColor(patternImage: UIImage(named: "image.png")!)
The blur effect that you found looks good. I am having trouble figuring out how it would work though. Probably set it using the backgroundView property.
I'll update if I find the answer.
Update:
Here is an idea of something that might work for blurring the cells.
Create a cocoa-touch class that sub-classes from UICollectionViewCell, then add this code to it:
convenience override init(frame: CGRect) {
self.init(frame: frame)
var blurEffect: UIVisualEffect
blurEffect = UIBlurEffect(style: .Light)
var visualEffectView: UIVisualEffectView
visualEffectView = UIVisualEffectView(effect: blurEffect)
visualEffectView.frame = self.maskView!.bounds
self.addSubview(visualEffectView)
}
override func layoutSubviews() {
super.layoutSubviews()
self.maskView!.frame = self.contentView.bounds
}
Then in the CollectionViewController file, in the viewDidLoad, change this line of code:
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Change UICollectionViewCell.self to <Name of CollectionViewCell file>.self
Result:
1) First of all, I think you need to change how you look at that layout. There are no separators. Just UICollectionView Cells with spacing between cells, lowered opacity and some blur.
This settings will give you something close to image you posted, you can edit it for your needs later:
On storyboard go to your UICollectionView's size inspector.
Min Spacing-> For Cells = 2, For Lines = 2.
Section Insets-> Left = 7, Right = 7.
2) I'm using this on my app to divide space equally for 3 cells. Changed it for your settings. Just copy/paste and you are good to go.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
return CGSize(width: (screenWidth/3)-6, height: (screenWidth/3)-6);
}
}
And as the last step put two images on top of CollectionView, to the left and right of the view and make widths equal to 7 and heights equal to UICollectionView. These images should have same opacity/background with cells. This will make it look like the image you want.
I hope my answer works for you. Good luck.
The first thing I would like to say is, your all above result can be achieved from UICollectionViewFlowLayout, Which is the default layout for UICollectionView.
UICollectionViewDelegateFlowLayout has all of the methods that can fulfill your requirements.
The flowLayout has minimumLineSpacingForSectionAtIndex and minimumInteritemSpacingForSectionAtIndexfor giving the spacing between the cells(both horizontally and vertically).
Its not a good way of giving cell frame in cellForItemAtIndexPath (like you submit the answer link). For that flowLayout provides a delegate for sizing cell sizeForItemAtIndexPath.
About the third question, yes you can use UIVisualEffectView for bluring purpose but compatible for only after iOS 8 and has issue with iPad2 I guess. But for your problem I would blur each cell rather than collectionView itself(since cell spacing is not blur).
I cannot find anything about the "separators" (the line that divides all the boxes). I like that it doesn't touch the screen borders horizontally. Is it done programmatically?
Yes, it looks like it is rendered on to a layer. You should read the Quartz 2D Programming Guide to get a handle on drawing and working with layers.
To divide the space equally in all devices (3 boxes/buttons horizontally) I found this answer answer. Is this the right approach?
This would be an option, but would not give you the separators look you like from your screen shot.
I would have my cell view's backgroundColor is set to clearColor, and then set the UICollectionView's backgroundView property to a view containing your separators and the blur effect. Make sure the UICollectionView's backgroundColor property is set to clearColor.
About the third question, yes you can use UIVisualEffectView for bluring purpose but compatible for only after iOS 8 and has issue with iPad2 I guess. But for your problem I would blur each cell rather than collectionView itself(since cell spacing is not blur).
If you use the backgroundView property of the UICollectionView to handle your separators and blur then your cells would only need to have their backgroundColor set to clearColor.
You should note that there is more than one way to do this, each way will have it's own drawbacks choose what works for you best.
I have created this meethod for custom layout. You can use by modifying according to your request.
func setCollectionLayout() {
let layout:UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top:0,left:0,bottom:0,right:0)
layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width/2 - 1, height: 136)
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
collectionView.collectionViewLayout = layout
}