Creating an autolayout-based metrics view - ios

I have a reusable view I will be using in UITableViewCell's and UICollectionViewCell's, and need to get its dimensions for tableView:heightForRowAtIndexPath:. Some subviews have stuff going on inside layoutSubviews so I can't call systemLayoutForContentSize:, instead my plan is to:
Instantiate the metrics view.
Set the size to include the desired width.
Populate it with data.
Update constraints / Layout subviews.
Grab the height of the view or an internal "sizing" view.
The problem I'm running into is that I cannot force the view to layout without inserting it into the view and waiting for the runloop.
I've distilled a rather boring example. Here's View.xib. The subview is misaligned to highlight that the view is never getting laid out even to the baseline position:
On the main thread I call:
UIView *view = [[UINib nibWithNibName:#"View" bundle:nil] instantiateWithOwner:nil options:nil][0];
NSLog(#"Subviews: %#", view.subviews);
view.frame = CGRectMake(0, 0, 200, 200);
[view updateConstraints];
[view layoutSubviews];
NSLog(#"Subviews: %#", view.subviews);
[self.view addSubview:view];
[view updateConstraints];
[view layoutSubviews];
NSLog(#"Subviews: %#", view.subviews);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Subviews: %#", view.subviews);
});
I get out the following view information:
1) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
2) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
3) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
4) "<UIView: 0x8bad9e0; frame = (0 100; 100 100); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
1 indicates that the fresh-out-of-the-NIB view hasn't been laid out. 2 indicates that updateConstraints/layoutSubviews did nothing. 3 indicates that adding it to the view hierarchy did nothing. 4 finally indicates that adding to the view hierarchy and one pass through the main-loop laid out the view.
I would like to get to the point where I can get the view's dimensions without having to let the application handle it or perform manual calculations (string height + constraint1 + constraint2) on my own.
Update
I've observed that if I place view inside a UIWindow I get a slight improvement:
UIView *view = [[UINib nibWithNibName:#"View" bundle:nil] instantiateWithOwner:nil options:nil][0];
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
view.frame = CGRectMake(0, 0, 200, 200);
[window addSubview:view];
[view layoutSubviews];
If view.translatesAutoresizingMaskIntoConstraints == YES, the view's immediate subviews will be laid out, but none of their children.

The Autolayout Question
In the basic case you mentioned, you can get the correct size by calling setNeedsLayout and then layoutIfNeeded on the container view.
From the UIView class reference on layoutIfNeeded:
Use this method to force the layout of subviews before drawing. Starting with the receiver, this method traverses upward through the view hierarchy as long as superviews require layout. Then it lays out the entire tree beneath that ancestor. Therefore, calling this method can potentially force the layout of your entire view hierarchy. The UIView implementation of this calls the equivalent CALayer method and so has the same behavior as CALayer.
I don't think the "entire view hierarchy" applies to your use case since the metrics view presumably wouldn't have a superview.
Sample Code
In a sample empty project, with just this code, the correct frame is determined after layoutIfNeeded is called:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) UIView *redView;
#end
#implementation ViewController
#synthesize redView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
redView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 220, 468)];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
NSLog(#"Red View frame: %#", NSStringFromCGRect(redView.frame));
// outputs "Red View frame: {{50, 50}, {220, 468}}"
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[redView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(redView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[redView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(redView)]];
NSLog(#"Red View frame: %#", NSStringFromCGRect(redView.frame));
// outputs "Red View frame: {{50, 50}, {220, 468}}"
[self.view setNeedsLayout];
NSLog(#"Red View frame: %#", NSStringFromCGRect(redView.frame));
// outputs "Red View frame: {{50, 50}, {220, 468}}"
[self.view layoutIfNeeded];
NSLog(#"Red View frame: %#", NSStringFromCGRect(redView.frame));
// outputs "Red View frame: {{0, 100}, {100, 100}}"
}
#end
Additional Considerations
Slightly outside the scope of your question, here are some other issues you may run into, since I've worked on this exact problem in a real app:
Calculating this in heightForRowAtIndexPath: might be expensive, so you may want to precalculate and cache the results
Precalculation should be done on a background thread, but UIView layout doesn't work well unless it's done on the main thread
You should definitely implement estimatedHeightForRowAtIndexPath: to reduce the impact of these performance issues
Using intrinsicContentSize
In response to:
Some subviews have stuff going on inside layoutSubviews so I can't call systemLayoutForContentSize:
You can use this method if you implement intrinsicContentSize, which lets a view suggest an optimal size for itself. One implementation for this might be:
- (CGSize) intrinsicContentSize {
[self layoutSubviews];
return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame));
}
This simple approach will only work if your layoutSubviews method doesn't refer to an already-set size (like self.bounds or self.frame). If it does, you may need to do something like:
- (CGSize) intrinsicContentSize {
self.frame = CGRectMake(0, 0, 10000, 10000);
while ([self viewIsWayTooLarge] == YES) {
self.frame = CGRectInset(self.frame, 100, 100);
[self layoutSubviews];
}
return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame));
}
Obviously, you'd need to adjust these values to match the particular layout of each view, and you may need to tune for performance.
Finally, I'll add that due in part to the exponentially increasing cost of using auto-layout, for all but the simplest table cells, I usually wind up using manual height calculation.

Presumably you're calling the demo code when the view controller first loads its view, like in viewDidLoad or another life cycle method. The nested subview's geometries won't reflect its constraints until viewDidLayoutSubviews is called. Nothing you do during the initial life cycle of a view controller will make that method arrive any faster.
Update 12/30/13: After testing Aaron Brager's sample code, I now realize that the previous paragraph is incorrect. Apparently, you can force layout in viewDidLoad by calling setNeedsLayout followed by layoutIfNeeded.
If you executed the demo code in response to a button click instead, I think you'll see the final geometries of your nested subview logged before the action method completes.
- (IBAction)buttonTapped:(id)sender
{
UIView *view = [[UINib nibWithNibName:#"View" bundle:nil] instantiateWithOwner:nil options:nil][0];
view.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:view];
[self.view layoutIfNeeded];
NSLog(#"Subviews: %#", view.subviews);
}
In the latter case, you can request layout on-demand because the view controller has completed its initial setup.
But during a view controller's initial setup, how are you going to get the final geometries of your re-usable subview?
After you set the content for the re-usable subview, have your view controller ask the subview for its size. In other words, implement a method on your custom view that calculates the size based on the content.
For example, if the subview's content is an attributed string, you could use a method like boundingRectWithSize:options:context: to help determine the size of your subview.
CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsersLineFragmentOrigin context:nil];

Related

When to layout subviews of UIViewControllers view?

In my UIViewController class, I'm creating a UIView called safeAreaView and adding it as a subview to the UIViewControllers view property. I'm making it so safeAreaView takes up the entire safe area of the UIViewControllers view property:
- (void) viewDidLoad
{
[self setToolbarWithColor: self.mainToolbarColor animated:NO];
self.tapGestureRecognizer.delegate = self;
self.view.clipsToBounds = YES;
self.view.backgroundColor = [UIColor lightGrayColor];
self.safeAreaView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
self.safeAreaView.clipsToBounds = YES;
self.safeAreaView.delegate = self;
self.safeAreaView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview: self.safeAreaView];
[self.safeAreaView.leadingAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.leadingAnchor].active = YES;
[self.safeAreaView.trailingAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.trailingAnchor].active = YES;
[self.safeAreaView.topAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.topAnchor].active = YES;
[self.safeAreaView.bottomAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.bottomAnchor].active = YES;
[self.safeAreaView loadSubviews];
}
This works fine, but my problem is, at some point after this during the UIViewControllers initialization cycle, safeAreaView updates to account for the statusbar (it's y position moves up 20 and it decreases in size by 20).
I need to layout some subviews on safeAreaView and I don't know the proper time? If I attach the subviews like above, they have the wrong height. And I can't use some auto layout features on the subviews because there are specific things that I need to do. I've also tried executing the above code in viewWillAppear with no luck.
Wondering if anyone had any suggestions?
You can override - (void)layoutSubviews on your safeAreaView class:
- (void)layoutSubviews {
[super layoutSubviews];
// Manual frame adjustment for non-autolayout participating subviews
// ...
}
Another option would be to override your safeAreaView's class frame setter, so each time the frame of your view changes, you'll get a chance to manually set any subview frames as needed.

When should I be setting borders in UIViews?

I've got a UIView that does not fill the whole screen and I would like to add a top border to that view. However, I keep getting the following:
Here is the code I am using:
CGFloat thickness = 4.0f;
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(0, 0, self.announcementCard.frame.size.width, thickness);
topBorder.backgroundColor = [UIColor blueColor].CGColor;
How I know why the border goes off the screen. This is because I put the border on the view inside the UIViews init method. When I do this the self.announcementCard.frame.size.width is 1000 and hence why the border goes off the screen. The self.announcementCard.frame.size.width has a width and height of 1000. The reason for this is because the UIView hasn't added the constraints to the UIView in its init methods.
Thus, my question is when should I be calling the code I've written above? When will self.announcementCard.frame.size.width have its constraints added to it and have its frame updated?
You should add your subviews (or sublayers) in the viewDidLoad method. However if you are using the auto-layout keep a reference of your sublayer and update it in the viewDidLayoutSubviews method:
- (void)viewDidLoad {
[super viewDidLoad];
_borderLayer = [CALayer layer];
[self.view.layer addSublayer:_borderLayer];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_borderLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, 3);
}
Otherwise you can simply clipsToBounds the view to avoid the subviews to be visible beyond the bounds.
self.view.clipsToBounds = YES;
in the init the graphics isn't made yet. You have to put all your configuration on graphics object in the viewDidLoad: or viewWillAppear: of the UIViewController.
Short Answer:
viewWillAppear
By the time viewWillAppear is called, your subviews have been laid out and the frames are valid. Doing frame-based calculations in viewDidLoad can often have issues since the frames have not been set.

UIScrollView - content goes out bound when scrolling

I design the scrollview in interface builder like this
It looks good here. But unfortunately when I run it on emulator or device
it becomes
The content in scrollview is expand outside scrollview itself and even though outside UIView that contains this scrollView.
In my viewDidLoad (panel is the container of scrollView )
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
CGFloat adjustPanelHeight = [PTTScreenScaleUtil getAdjustHeight:self.panel.frame.size.height];
CGRect panelRect = self.panel.frame;
panelRect.size.height = adjustPanelHeight;
self.panel.frame = panelRect;
UIImage *image = [UIImage imageNamed:#"panel-background"];
UIGraphicsBeginImageContext(self.panel.frame.size);
[image drawInRect:CGRectMake(0, 0, self.panel.frame.size.width, adjustPanelHeight)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.panel setBackgroundColor:[UIColor colorWithPatternImage:newImage]];
NSLog(#"scrollView Height : %f", self.scrollView.frame.size.height);
NSLog(#"scrollView contentSize Height : %f", self.scrollView.contentSize.height);
// CGRect scrollViewRect = self.scrollView.frame;
// CGRect scrollViewContentRect = self.scrollView.frame;
// NSLog(#"ScrollView Height Before : %f , After : %f", self.scrollView.frame.size.height, [PTTScreenScaleUtil getAdjustHeight:self.scrollView.frame.size.height]);
// scrollViewRect.size.width = 280;
// scrollViewRect.size.height = [PTTScreenScaleUtil getAdjustHeight:270];
// self.scrollView.frame = scrollViewRect;
// [self.detailsLabel sizeToFit];
UIView *view = [[self.scrollView subviews] objectAtIndex:0];
// [view sizeToFit];
// [self.scrollView setContentMode:UIViewContentModeScaleAspectFit];
// NSLog(#"ContentSize Height : %f", view.frame.size.height);
// scrollViewContentRect.size.height = view.frame.size.height;
NSLog(#"Bounds : %f", view.bounds.size.height);
self.scrollView.frame = CGRectMake(10, 10, 280, 270);
self.scrollView.contentSize = CGSizeMake(280, 500);
NSLog(#"Frame Height %f", self.scrollView.frame.size.height);
//[self.scrollView setContentSize: CGSizeMake(280, 1000)];
CGRect termBtnRect = self.termBtn.frame;
CGRect mailBtnRect = self.mailBtn.frame;
CGRect twitterBtnRect = self.twitterBtn.frame;
CGRect fbBtnRect = self.fbBtn.frame;
termBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
mailBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
twitterBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
fbBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
self.termBtn.frame = termBtnRect;
self.mailBtn.frame = mailBtnRect;
self.twitterBtn.frame = twitterBtnRect;
self.fbBtn.frame = fbBtnRect;
}
All the log return 270.0
PS. the scroll bar is correct even though the content goes outside but the scroll bar is working correctly (stay in the scrollview's frame as arrange in interface builder)
I have no idea how can I solve this.
Anyone help me please.
Thanks you.
Solve it by creating new view controller in interface builder and redo the same process with careful and bingo. It works.
When I compare both two view controller I realise that the wrong one UIScrollView Clip Subviews is unchecked. When check it the problem solve.
I just struggled with this for an hour and had a head smack moment.
In my case, I had a UIView on the scene in the Storyboard. At some point I decided I needed it to be a UIScrollView instead (as opposed to the original plan which was to embed the UIScrollView in a UIView)
I went ahead and changed the class on the UIView to UIScrollView. IB changed it to Scroll View in the Document Outline, I figure I'm good, right?
And then I see the behavior you describe.
At some point it hits me that this isn't sufficient. Apparently adding a UIScrollView via IB does some things differently than adding a UIView and just changing class isn't enough. And this is probably the reason re-doing it from scratch fixed it for you.
So for anyone who runs into this in the future, make sure you added the UIScrollView via IB instead of a UIView
I was having the same problem as described in this post. I tried multiple combinations of solutions that did not work, including:
putting the scroll view inside a view with Clip To Bounds = YES
putting a view inside the scroll view with Clip To Bounds = YES, that then contained my child view
putting a Container View inside the scroll view, and then embedding my subview
rebuilding the Interface Builder files completely
every combination of autosizing mask options systematcially for both the scroll view and container view
clip to bounds enabled or disabled for every single element systematically
The child view in question had previously worked inside a scroll view, but wouldn't in this one case where the content blew outside the bounds of the scroll view.
In the end, I implemented the solution in code as I could find no way to get Interface Builder to co-operate:
// There are two scroll areas on the screen, the left view and the right view.
// We want the right view to contain a scrollable area with another child view controller
// we designed in Interface Builder.
// create a scroll view to fill the right view with a scrollable area
CGSize rightFrameSize = self.rightView.bounds.size;
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake (0, 0, rightFrameSize.width, rightFrameSize.height)];
scrollView.contentSize = CGSizeMake(640, 1352);
[self.rightView addSubview:scrollView];
// now create our child view controller from Interface Builder and add it to the scroll view
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"CustomerAddress" bundle:[NSBundle mainBundle]];
detailsView = [sb instantiateViewControllerWithIdentifier:#"CustomerDetailsView"];
detailsView.delegate = self;
detailsView.customer = _customer;
[scrollView addSubview:detailsView.view];
You could of course get the scrollView.contentSize from the child view controller you constructed in Interface Builder using scrollView.contentSize = detailsView.view.frame.size.
I have a love/hate relationship with Interface Builder... most days I love it, but some days we argue and I wish we'd never met... :)
only make cliptobound=YES in storyboard if you changed UIView to UIScrollView

How to get frame of subview after apply transform on mainView?

I have created mainView objcet of UIView and added one subview on it. I applied transform on mainView for reducing frame size. But frame of subview of mainView was not reduced. How to reduce the size of this subview.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGFloat widthM=1200.0;
CGFloat heightM=1800.0;
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, widthM, heightM)];
mainView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"te.png"]];
[self.view addSubview:mainView];
CGFloat yourDesiredWidth = 250.0;
CGFloat yourDesiredHeight = yourDesiredWidth *heightM/widthM;
CGAffineTransform scalingTransform;
scalingTransform = CGAffineTransformMakeScale(yourDesiredWidth/mainView.frame.size.width, yourDesiredHeight/mainView.frame.size.height);
mainView.transform = scalingTransform;
mainView.center = self.view.center;
NSLog(#"mainView:%#",mainView);
UIView *subMainView= [[UIView alloc] initWithFrame:CGRectMake(100, 100, 1000, 1200)];
subMainView.backgroundColor = [UIColor redColor];
[mainView addSubview:subMainView];
NSLog(#"subMainView:%#",subMainView);
}
NSlog of these views:
mainView:<UIView: 0x8878490; frame = (35 62.5; 250 375); transform = [0.208333, 0, 0, 0.208333, 0, 0]; layer = <CALayer: 0x8879140>>
subMainView:<UIView: 0x887b8c0; frame = (100 100; 1000 1200); layer = <CALayer: 0x887c160>>
Here the width of mainView is 250, the width of subview is 1000. but when i get the output in simulator, subview is occupied correctly, but it's not cross the mainView. How it is possible? How to get frame of subview with respect mainView frame after transformation?
What you're seeing is expected behavior. The frame of an UIView is relative to its parent, so it doesn't change when you apply a transformation to its superview. While the view will appear 'distorted' too, the frame won't reflect the changes since it's still at exact the same position relative to its parent.
However, I assume you would like to get the frame of the view relative to the topmost UIView. In that case UIKit offers these functions:
– [UIView convertPoint:toView:]
– [UIView convertPoint:fromView:]
– [UIView convertRect:toView:]
– [UIView convertRect:fromView:]
I applied these to your example:
CGRect frame = [[self view] convertRect:[subMainView frame] fromView:mainView];
NSLog(#"subMainView:%#", NSStringFromCGRect(frame));
And this is the output:
subMainView:{{55.8333, 83.3333}, {208.333, 250}}
In addition to s1m0n answer, the beautiful thing about applying a transform matrix to your view, is that you can keep reasoning in terms of its original coordinate system (in your case, you can handle subMainView using the non-transformed coordinate system, which is why, even though subMainView's frame is bigger than mainView's transformed frame, it still doesn't cross the parent view, as it gets automatically transformed). This means that when you have a transformed parent view (for example rotated and scaled) and you want to add a subview in a particular point relative to this parent view, you don't have to first keep track of the previous transformations in order to do so.
If you really are interested in knowing the subview's frame in terms of the transformed coordinate system, it will be enough to apply the same transformation to the subview's rectangle with:
CGRect transformedFrame = CGRectApplyAffineTransform(subMainView.frame, mainView.transform);
If you then NSLog this CGRect, you will obtain:
Transformed frame: {{20.8333, 20.8333}, {208.333, 250}}
Which, I believe, are the values that you were looking for. I hope this answers your question!

Setting the accessibilityFrame of an element whose parent view will move

I have a UITableView with a custom header (i.e. I create the UIView myself). I need to tweak the accessibilityFrame of one of the subviews of the view, but I can’t figure out how to set the coordinates of the frame appropriately—they need to be relative to the window, but I’m not sure how to accomplish that.
My code looks like
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger) section
{
CGRect bounds = CGRectMake(0, 0, [tableView frame].size.width, 48);
UIView *header = [[UIView alloc] initWithFrame:bounds];
UILabel *labelOne = [[UILabel alloc] initWithFrame:
CGRectMake(0, 0, bounds.size.width - 80, 18)];
UILabel *labelTwo = [[UILabel alloc] initWithFrame:
CGRectMake(0, 20, bounds.size.width - 80, 18)];
CGRect frameOne = [labelOne frame];
CGRect frameTwo = [labelTwo frame];
[labelTwo setIsAccessibilityElement:NO];
[labelOne setAccessibilityFrame:CGRectUnion(frameOne, frameTwo)];
// ...
return header;
}
I’ve got two UILabels, which I want to combine into one for the purposes of VoiceOver. I accomplish this by ignoring the second label and extending the frame of the first label to cover the area of the second label. (The second label is immediately below the first.) The problem is getting the frames. If I use the code as shown above, the accessibility frame is the correct size, but is positioned as if the UITableView’s header were in the top left corner of the screen. I tried to modify the code to say
CGRect frameOne = [header convertRect:[labelOne frame] toView:nil];
CGRect frameTwo = [header convertRect:[labelTwo frame] toView:nil];
but the same thing happened. Shouldn’t this latter piece of code convert the UILabels’ frames into window-relative coordinates?
I thought maybe the issue is that when the UIView is created, it doesn’t know where on screen it’s going to be positioned (and as part of a UITableView it may be scrolled all over the place). Is it necessary to implement accessibilityFrame as a message which checks the UIView’s position each time it is called?
There's a helper function that will assist you with doing exactly that: UIAccessibilityConvertFrameToScreenCoordinates. This function takes a CGRect and converts it from a view's coordinate system into screen coordinates.
I don't think it's the timing of when the UIView is created, as I believe the window should be not-nil by the time tableView:viewForHeaderInSection: is called. I think the problem is the receiver of the convertRect:toView: message. Rather than passing this message to header, you should be passing it to [self view].
You're converting from the receivers coordinate system to that of another view, in this case nil or the UIWindow in your app. When header receives this message, you're converting from header's coordinate system to window's coordinate system, but header itself is a subview of [self view]. Instead, you want to ask [self view] to do the conversion, which should take into account any UINavigationBar's, etc.
CGRect frameOne = [[self view] convertRect:[labelOne frame] toView:nil];
CGRect frameTwo = [[self view] convertRect:[labelTwo frame] toView:nil];

Resources