Zombie crash because of Swift inheritance - ios

I have Swift class (derived of UIView), which I call in Objective C++ code.
Initially, it worked well and looked like this:
#objc
public class HorizonView: UIView {
var dashGap: (CGFloat, CGFloat)? = nil
let titleLayer = HorizonTextLayer()
let angleLayer = HorizonTextLayer()
let dashLayer = CAShapeLayer()
override public init(frame: CGRect) {
super.init(frame: frame)
setUpLayers()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpLayers()
}
public override var frame: CGRect {
didSet {
setUpLayers()
}
}
// other stuff here ...
}
Objective C++ calls to alloc view:
_horizonView = [[HorizonView alloc] initWithFrame:CGRectMake(10, 10, 200, 30)];
[bigView addSubview:_horizonView];
Objective C++ calls to delete view:
if ( _horizonView ) { [_horizonView removeFromSuperview]; [_horizonView release]; _horizonView = nil; }
I decided to encapsulate HorizonView by exposing only one public method. I don't know if is possible to declare a protocol, which is subclass of UIView (protocol isn't a class, is it?), so I came to class:
#objc
public class HorizonView: UIView {
static func create(p1: CGPoint, andP2 p2: CGPoint) -> HorizonView {
let implFrame = CGRect(x: p1.x, y: p1.y-10, width: p2.x-p1.x, height: 20)
let impl = HorizonViewImpl(frame: implFrame)
return impl
}
}
private class HorizonViewImpl: HorizonView {
var dashGap: (CGFloat, CGFloat)? = nil
let titleLayer = HorizonTextLayer()
let angleLayer = HorizonTextLayer()
// old class code ...
}
Now Objective C++ alloc call looks so:
if ( _horizonView ) { [_horizonView removeFromSuperview]; [_horizonView release]; _horizonView = nil; }
_horizonView = [HorizonView createWithP1:CGPointMake(10, 10)
andP2:CGPointMake(210,40)];
[EGLV addSubview:_horizonView];
And I constantly receive a crash, when profiling, seeing this:
Could you give some hints to detect the problem?
Can such inheritance approach have some flaws?

Related

What is iOS's equivalent of Java/Swing's FlowLayout?

I'm trying to build a UIView that lays out other views using their intrinsic width, left to right, and then wraps if necessary. UICollectionView seems overly complicated for what I need. I also tried using the FlexLayout wrapper for Yoga, and couldn't seem to get the right incantation. It seems like it should be much easier than this -- what's the simple solution?
I ended up writing my own UIView to solve the problem. I doubt if it's very robust, but it solves my immediate needs. Please, comment if you have an improvements to suggest.
import UIKit
class FlowLayout: UIView {
private var hgapValue: CGFloat = 0.0
private var vgapValue: CGFloat = 0.0
var hgap: CGFloat {
set(newValue) {
hgapValue = (newValue < 0) ? -newValue : newValue
}
get {
return hgapValue
}
}
var vgap: CGFloat {
set(newValue) {
vgapValue = (newValue < 0) ? -newValue : newValue
}
get {
return vgapValue
}
}
//initWithFrame to init view from code
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
//initWithCode to init view from xib or storyboard
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func didAddSubview(_ subview: UIView) {
refreshLayout()
}
//common func to init our view
private func setupView() {
// backgroundColor = .red
}
public func refreshLayout() {
var x: CGFloat = 0.0
var y: CGFloat = 0.0
for subview in subviews {
subview.frame.origin.x = x
subview.frame.origin.y = y
x += subview.frame.width + hgapValue
if x + subview.frame.width >= (subview.superview?.frame.width)! {
x = 0.0
y += subview.frame.height + vgapValue
}
}
setNeedsDisplay()
}
}
In interface builder, using UIStackView to achieve a simple alternative.
You can also turn on auto layout to make it naturally.

How do i add dynamic behaviour in NSObject class : Swift

I'm trying to create a custom alertview with dynamic behaviour using UIDynamicAnimator in NSObject class using swift,While adding UISnapBehaviour to a view in NSObject class init method snap behaviour is not working,For instance look at the below code
import UIKit
class DynamicBehaviour: NSObject {
var Animator:UIDynamicAnimator!
var TargetView:UIView!
var TestView:UIView!
override init() {
super.init()
}
init(SourceViews:UIView) {
super.init()
TestView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
TestView.backgroundColor = UIColor.blueColor()
TargetView.addSubview(TestView)
Animator = UIDynamicAnimator(referenceView: TargetView)
let snapBehavior: UISnapBehavior = UISnapBehavior(item: TestView, snapToPoint: TargetView.center)
Animator.addBehavior(snapBehavior)
}
}
"TestView" is added as subview to "Target" but snap behaviour is not working.
I tried the same code in ObjectiveC
#import "DynamicBehaviour.h"
#import <UIKit/UIKit.h>
#interface DynamicBehaviour ()
#property(nonatomic,strong)UISnapBehavior * Snap_behaviour;
#property (nonatomic,strong)UIDynamicAnimator * Animator;
#end
#implementation DynamicBehaviour
-(instancetype)initWithSourceView:(UIView *)TargetView{
if (self = [super init]) {
UIView * TestView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
TestView.backgroundColor = [UIColor blueColor];
[TargetView addSubview:TestView];
self.Animator = [[UIDynamicAnimator alloc]initWithReferenceView:TargetView];
self.Snap_behaviour = [[UISnapBehavior alloc]initWithItem:TestView snapToPoint:TargetView.center];
[self.Animator addBehavior:self.Snap_behaviour];
}
return self;
}
#end
it works fine,the "TestView" snaps to the centre of TargetView.I don't know whats wrong with swift code.
Here's what I've tried:
The dynamic effect is working fine when coded in UIViewController class,problem exist only while subclassing NSObject in swift.
I have tried the other dynamic behaviours such as UIGravityBehavior the same problem exists in swift.
Done the same sample work in ObjectiveC object class its working fine,I've attached the working ObjC code too.
It seems that the problem may exist in init method or variable decleration am not sure about that
However, I don't know how to fix that problem. I've read through many articles on the internet. I've also searched StackOverflow. Please help.
Your code in Swift is not exactly the same as Objective-C code: Objective-C code has strong reference Snap_behaviour, but Swift code has only local readonly variable.
Here is the Swift equivalent:
class DynamicBehaviour: NSObject {
var Animator:UIDynamicAnimator!
var snapBehaviour:UISnapBehavior!
var TargetView:UIView!
var TestView:UIView!
override init() {
super.init()
}
init(SourceViews:UIView) {
super.init()
TestView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
TestView.backgroundColor = UIColor.blueColor()
TargetView.addSubview(TestView)
Animator = UIDynamicAnimator(referenceView: TargetView)
snapBehaviour = UISnapBehavior(item: TestView, snapToPoint: TargetView.center)
Animator.addBehavior(snapBehaviour)
}
}
Usually I use UIDynamicBehaviour as base class for combined behaviors like this
class CombinedBehavior: UIDynamicBehavior {
lazy var collider: UICollisionBehavior = {
let lazilyCreatedCollider = UICollisionBehavior()
lazilyCreatedCollider.translatesReferenceBoundsIntoBoundary = true
lazilyCreatedCollider.collisionMode = UICollisionBehaviorMode.Everything
return lazilyCreatedCollider
}()
lazy var itemBehavior: UIDynamicItemBehavior = {
let lazilyCreatedBehavior = UIDynamicItemBehavior()
lazilyCreatedBehavior.allowsRotation = true
lazilyCreatedBehavior.elasticity = 0
lazilyCreatedBehavior.resistance = 100
lazilyCreatedBehavior.angularResistance = 100
lazilyCreatedBehavior.friction = 100
return lazilyCreatedBehavior
}()
override init() {
super.init()
addChildBehavior(collider)
addChildBehavior(itemBehavior)
}
func addBarrier(path: UIBezierPath, named name: String) {
collider.removeBoundaryWithIdentifier(name)
collider.addBoundaryWithIdentifier(name, forPath: path)
}
func addView(view: UIView) {
dynamicAnimator?.referenceView?.addSubview(view)
collider.addItem(view)
itemBehavior.addItem(view)
}
func removeView(view: UIView) {
collider.removeItem(view)
itemBehavior.removeItem(view)
view.removeFromSuperview()
}
}
Put this code in your controller
lazy var animator: UIDynamicAnimator = {
let lazilyCreatedDynamicAnimator = UIDynamicAnimator(referenceView: self.view) // or any view you needed
// if you need delegate in your controller uncomment this
// lazilyCreatedDynamicAnimator.delegate = self
return lazilyCreatedDynamicAnimator
}()
var combinedBehavior = CombinedBehavior()
override func viewDidLoad() {
// your other code
animator.addBehavior(combinedBehavior)
}

Extend UIView with necessary dataSource and delegate

I want in UITableViewCell add customized UIView (will be analog segmented control)
I wrote subclass `protocol ITISegmentedViewDelegate: class {
func segmentedViewButtonChanged(index: Int)
}
public protocol ITISegmentedViewDataSource : NSObjectProtocol {
#available(iOS 2.0, *)
func segmentedView(itemsInSegmentedView: ITISegmentedView) -> [String]
}
public class ITISegmentedView: UIView {
var delegate: ITISegmentedViewDelegate?
var dataSource: ITISegmentedViewDataSource?
var selectedItem = -1
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.addButtons()
}
private func addButtons(){
if delegate == nil || dataSource == nil{
return
}
let height = frame.height
let width = frame.width
let array = dataSource!.segmentedView(self)
let totalItem = array.count
var startX = CGFloat(0)
for var index = 0; index < totalItem; ++index{
let button = UIButton(frame: CGRectMake(startX, 0, width/CGFloat(totalItem), height))
button.setTitle(array[index], forState: UIControlState.Normal)
button.tag = index
button.addTarget(button, action: "onButtonPressed", forControlEvents: .TouchUpInside)
startX += width/CGFloat(totalItem)
addSubview(button)
}
if totalItem>0{
selectedItem = 0
delegate?.segmentedViewButtonChanged(0)
}
}
func onButtonPressed(button: UIButton){
if selectedItem != button.tag{
delegate?.segmentedViewButtonChanged(button.tag)
selectedItem = button.tag
}
}
}`
In storyboard added UIView and set class ITISegmentedView
in my ViewController:
let cell = tableView.dequeueReusableCellWithIdentifier( cellName, forIndexPath: indexPath)
let seg = (cell.viewWithTag(1) as! ITISegmentedView)
seg.dataSource = self
seg.delegate = self
PROBLEM:
init(coder aDecoder: NSCoder) calls on dequeueReusableCell and at this moment data source and delegate is not set, so ITISegmentedView doesn't work.
Fall back to an empty dataSource when you encounter nil.
Also, try not to use !, but rather ? and ?? to always take into account that an Optional is... well. Optional.

Swift, iboutlet and custom controls

I may be doing something really stupid, but I don't seem to be able to use Interface Builder to connect IBOutlet variables to custom views, but only in Swift.
I've created a class called MyView, which extends from UIView. In my controller, I've got a MyView variable (declared as #IBOutlet var newView: MyView). I go into IB and drag a UIView onto the window and give it a class of MyView.
Whenever I've done similar in Objective C, I'm then able to click on the View Controller button at the top of the app window, select the variable and drag it down to the control to link the two together. When I try it in Swift, it refuses to recognise that the view is there.
If I change the class of the variable in the controller to UIView, it works fine. But not with my custom view.
Has anyone else got this problem? And is it a feature, or just my idiocy?
Code for Controller
import UIKit
class ViewController: UIViewController {
#IBOutlet var newView:MyView
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Code for view
import UIKit
class MyView: UIView {
init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect)
{
// Drawing code
}
*/
}
I've had a similar problem, and I think it's partially a caching issue and partially just an Xcode6/Swift issue. The first step I found was required was to make sure that the view controller .swift file would be loaded in the Assistant Editor when choosing "automatic".
With Xcode finding that both the files are linked I could sometimes control-drag from the view/button/etc. from the IB to the .swift file, but often had to drag from the empty circle in the gutter of the #IBOutlet var newView:MyView line to the view I wanted it to match up to.
If you can't get the file to load in the Assistant Editor then I found that doing the following would often work:
Remove the custom class from the IB view
Clean the project (cmd + K)
Close/reopen Xcode
Possibly clean again?
Add the custom class back to the view
Hope it works :)
If that seems to get you half way/nowhere add a comment and I'll see if it triggers anything else I did
In my case import UIKit was missing, after adding this line I could create an IBOutlet from Storyboard again.
I've had a similar problem to the one described in this thread. Maybe you found a solution maybe not but anybody who encounters this in the future. I've found the key is to use the "required init" function as follows:
required init(coder aDecoder: NSCoder) {
print("DrawerView: required init")
super.init(coder: aDecoder)!
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
self.userInteractionEnabled = true
addCustomGestureRecognizer()
}
This is the complete class of my custom view:
import UIKit
import Foundation
class DrawerView: UIView {
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
var drawerState: Int = 0
override init (frame : CGRect) {
print("DrawerView: main init")
super.init(frame : frame)
}
override func layoutSubviews() {
print("DrawerView: layoutSubviews")
super.layoutSubviews()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
print("DrawerView: required init")
super.init(coder: aDecoder)!
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
self.userInteractionEnabled = true
addCustomGestureRecognizer()
}
func addCustomGestureRecognizer (){
print("DrawerView: addCustomGestureRecognizer")
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.handleDrawerSwipeGesture(_:)))
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.addGestureRecognizer(swipeDown)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.handleDrawerSwipeGesture(_:)))
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
self.addGestureRecognizer(swipeUp)
print("DrawerView self: \(self)")
}
func minimizeDrawer(){
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: {
// let height = self.bookButton.frame.size.height
// let newPosY = (self.screenHeight-64)*0.89
// print("newPosY: \(newPosY)")
self.setY(self.screenHeight*0.86)
}, completion: { finished in
self.drawerState = 0
for view in self.subviews {
if let _ = view as? UIButton {
let currentButton = view as! UIButton
currentButton.highlighted = false
} else if let _ = view as? UILabel {
let currentButton = view as! UILabel
if self.tag == 99 {
currentButton.text = "hisotry"
} else if self.tag == 999 {
currentButton.text = "results"
}
}
}
})
}
func handleDrawerSwipeGesture(gesture: UIGestureRecognizer) {
print("handleDrawerSwipeGesture: \(self.drawerState)")
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch self.drawerState{
case 0:
if swipeGesture.direction == UISwipeGestureRecognizerDirection.Down {
// nothing to be done, mini and swiping down
print("mini: !")
} else {
// mini and swiping up, should go to underneath city box
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: {
let toYPos:CGFloat = 128 + 64 + 8
self.setY(toYPos)
}, completion: { finished in
self.drawerState = 1
for view in self.subviews {
if let _ = view as? UIButton {
let currentButton = view as! UIButton
currentButton.highlighted = true
} else if let _ = view as? UILabel {
let currentLabel = view as! UILabel
currentLabel.text = "close"
}
}
})
}
break;
case 1:
if swipeGesture.direction == UISwipeGestureRecognizerDirection.Down {
// open and swiping down
self.minimizeDrawer()
} else {
// open and swiping up, nothing to be done
}
break;
default:
break;
}
}
}
}
Hope this helps...

Core animation progress callback

Is there an easy way to be called back when a Core Animation reaches certain points as it's running (for example, at 50% and 66% of completion ?
I'm currently thinking about setting up an NSTimer, but that's not really as accurate as I'd like.
I've finally developed a solution for this problem.
Essentially I wish to be called back for every frame and do what I need to do.
There's no obvious way to observe the progress of an animation, however it is actually possible:
Firstly we need to create a new subclass of CALayer that has an animatable property called 'progress'.
We add the layer into our tree, and then create an animation that will drive the progress value from 0 to 1 over the duration of the animation.
Since our progress property can be animated, drawInContext is called on our sublass for every frame of an animation. This function doesn't need to redraw anything, however it can be used to call a delegate function :)
Here's the class interface:
#protocol TAProgressLayerProtocol <NSObject>
- (void)progressUpdatedTo:(CGFloat)progress;
#end
#interface TAProgressLayer : CALayer
#property CGFloat progress;
#property (weak) id<TAProgressLayerProtocol> delegate;
#end
And the implementation:
#implementation TAProgressLayer
// We must copy across our custom properties since Core Animation makes a copy
// of the layer that it's animating.
- (id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self) {
TAProgressLayer *otherLayer = (TAProgressLayer *)layer;
self.progress = otherLayer.progress;
self.delegate = otherLayer.delegate;
}
return self;
}
// Override needsDisplayForKey so that we can define progress as being animatable.
+ (BOOL)needsDisplayForKey:(NSString*)key {
if ([key isEqualToString:#"progress"]) {
return YES;
} else {
return [super needsDisplayForKey:key];
}
}
// Call our callback
- (void)drawInContext:(CGContextRef)ctx
{
if (self.delegate)
{
[self.delegate progressUpdatedTo:self.progress];
}
}
#end
We can then add the layer to our main layer:
TAProgressLayer *progressLayer = [TAProgressLayer layer];
progressLayer.frame = CGRectMake(0, -1, 1, 1);
progressLayer.delegate = self;
[_sceneView.layer addSublayer:progressLayer];
And animate it along with the other animations:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"progress"];
anim.duration = 4.0;
anim.beginTime = 0;
anim.fromValue = #0;
anim.toValue = #1;
anim.fillMode = kCAFillModeForwards;
anim.removedOnCompletion = NO;
[progressLayer addAnimation:anim forKey:#"progress"];
Finally, the delegate will be called back as the animation progresses:
- (void)progressUpdatedTo:(CGFloat)progress
{
// Do whatever you need to do...
}
If you don't want to hack a CALayer to report progress to you, there's another approach. Conceptually, you can use a CADisplayLink to guarantee a callback on each frame, and then simply measure the time that has passed since the start of the animation divided by the duration to figure out the percent complete.
The open source library INTUAnimationEngine packages this functionality up very cleanly into an API that looks almost exactly like the UIView block-based animation one:
// INTUAnimationEngine.h
// ...
+ (NSInteger)animateWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
animations:(void (^)(CGFloat percentage))animations
completion:(void (^)(BOOL finished))completion;
// ...
All you need to do is call this method at the same time you start other animation(s), passing the same values for duration and delay, and then for each frame of the animation the animations block will be executed with the current percent complete. And if you want peace of mind that your timings are perfectly synchronized, you can drive your animations exclusively from INTUAnimationEngine.
I made a Swift (2.0) implementation of the CALayer subclass suggested by tarmes in the accepted answer:
protocol TAProgressLayerProtocol {
func progressUpdated(progress: CGFloat)
}
class TAProgressLayer : CALayer {
// MARK: - Progress-related properties
var progress: CGFloat = 0.0
var progressDelegate: TAProgressLayerProtocol? = nil
// MARK: - Initialization & Encoding
// We must copy across our custom properties since Core Animation makes a copy
// of the layer that it's animating.
override init(layer: AnyObject) {
super.init(layer: layer)
if let other = layer as? TAProgressLayerProtocol {
self.progress = other.progress
self.progressDelegate = other.progressDelegate
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
progressDelegate = aDecoder.decodeObjectForKey("progressDelegate") as? CALayerProgressProtocol
progress = CGFloat(aDecoder.decodeFloatForKey("progress"))
}
override func encodeWithCoder(aCoder: NSCoder) {
super.encodeWithCoder(aCoder)
aCoder.encodeFloat(Float(progress), forKey: "progress")
aCoder.encodeObject(progressDelegate as! AnyObject?, forKey: "progressDelegate")
}
init(progressDelegate: TAProgressLayerProtocol?) {
super.init()
self.progressDelegate = progressDelegate
}
// MARK: - Progress Reporting
// Override needsDisplayForKey so that we can define progress as being animatable.
class override func needsDisplayForKey(key: String) -> Bool {
if (key == "progress") {
return true
} else {
return super.needsDisplayForKey(key)
}
}
// Call our callback
override func drawInContext(ctx: CGContext) {
if let del = self.progressDelegate {
del.progressUpdated(progress)
}
}
}
Ported to Swift 4.2:
protocol CAProgressLayerDelegate: CALayerDelegate {
func progressDidChange(to progress: CGFloat)
}
extension CAProgressLayerDelegate {
func progressDidChange(to progress: CGFloat) {}
}
class CAProgressLayer: CALayer {
private struct Const {
static let animationKey: String = "progress"
}
#NSManaged private(set) var progress: CGFloat
private var previousProgress: CGFloat?
private var progressDelegate: CAProgressLayerDelegate? { return self.delegate as? CAProgressLayerDelegate }
override init() {
super.init()
}
init(frame: CGRect) {
super.init()
self.frame = frame
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.progress = CGFloat(aDecoder.decodeFloat(forKey: Const.animationKey))
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(Float(self.progress), forKey: Const.animationKey)
}
override class func needsDisplay(forKey key: String) -> Bool {
if key == Const.animationKey { return true }
return super.needsDisplay(forKey: key)
}
override func display() {
super.display()
guard let layer: CAProgressLayer = self.presentation() else { return }
self.progress = layer.progress
if self.progress != self.previousProgress {
self.progressDelegate?.progressDidChange(to: self.progress)
}
self.previousProgress = self.progress
}
}
Usage:
class ProgressView: UIView {
override class var layerClass: AnyClass {
return CAProgressLayer.self
}
}
class ExampleViewController: UIViewController, CAProgressLayerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let progressView = ProgressView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
progressView.layer.delegate = self
view.addSubview(progressView)
var animations = [CAAnimation]()
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 0
opacityAnimation.toValue = 1
opacityAnimation.duration = 1
animations.append(opacityAnimation)
let progressAnimation = CABasicAnimation(keyPath: "progress")
progressAnimation.fromValue = 0
progressAnimation.toValue = 1
progressAnimation.duration = 1
animations.append(progressAnimation)
let group = CAAnimationGroup()
group.duration = 1
group.beginTime = CACurrentMediaTime()
group.animations = animations
progressView.layer.add(group, forKey: nil)
}
func progressDidChange(to progress: CGFloat) {
print(progress)
}
}

Resources