How to handle button touch inside the UICollectionviewCell or UITableViewCell - ios

This is a problem that I always stay in dilemma when I creating cells.
Let's say I have a custom cell (PlaceCell) which I'm using in different controllers and collectionviews. It has a label which identify the place name (PlaceNameLabel) and will navigate to place detail when user taps on it.
This doesn't matter controller, collectionviews or wherever cell is used, this is independent of where it is used.
So I have to put the PlaceNameLabel's UITapGestureRecognizer code inside the PlaceCell class.
class PlaceCell: UICollectionViewCell {
#IBOutlet weak var placeName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
initEventListeners()
}
func initEventListeners() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(handlePlaceNameTouch))
}
func handlePlaceNameTouch() {
// I have to push a new view controller here
}
}
But if I want to push the place detail view controller I need to access the controller. If I want to access the controller I have two options and this is where I stay in dilemma, I have read lots of SO questions answers most of them suggest the second option, but I think the first one is better approach. But I don't know if it's problem to reference the controller inside the cell.
Could you please share your opinion or any other method to handle this problem?
FIRST OPTION
Referencing the controller inside the cell,
extension MyController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlaceCell", for: indexPath) as! PlaceCell
cell.controller = self
return cell
}
}
class PlaceCell: UICollectionViewCell {
var controller: UIViewController?
#IBOutlet weak var placeName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
initEventListeners()
}
func initEventListeners() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(handlePlaceNameTouch))
}
func handlePlaceNameTouch() {
controller.navigationController.pushViewController(PlaceDetailController(),
animated: true)
}
}
SECOND OPTION
Create protocol and delegate, pass event to controller and do whatever you want,
(I think this is not well, because action is about the cell and I have to create protocol function multiple times because I use this Cell in different Controllers)
protocol PlaceCellDelegate {
func handlePlaceNameTouch()
}
class PlaceCell: UICollectionViewCell {
var delegate: PlaceCellDelegate?
#IBOutlet weak var placeName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
initEventListeners()
}
func initEventListeners() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(handlePlaceNameTouch))
}
func handlePlaceNameTouch() {
delegate?.handlePlaceNameTouch()
}
}
extension MyController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, PlaceCellDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlaceCell", for: indexPath) as! PlaceCell
cell.delegate = self
return cell
}
func handlePlaceNameTouch() {
self.navigationController.pushViewController(PlaceDetailController(), animated: true)
}
}

You no need to handle specially to cell selection. No need to use UITapGestureRecognizer or no need implement your own protocol to detect cell selection.
UICollectionView and UITableView has its own protocols for that.
Cell selection delegate for UICollectionView - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
Cell selection delegate for UITableView - UITableViewDelegate protocol
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Confirm protocol & set delegate on your UIViewController
class viewcontroller : UICollectionViewDelegate {
#IBOutlet weak collectionView: UICollectionView!
func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
}
And implement the protocol method inside a view controller, which I mentioned above.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//indexPath - Index path of selected cell.
// Add you cell selection logic here.
}
This method will be triggered when you tap on the cell. Inside this method fetch the model from datasource array. and navigate to detail view based on which cell (model) selected by user.

While both your options are possible, if you are hesitating I would recommend the delegate approach as it is a more "swifty" way of doing it and makes the cell reusable.
Here is a third option using an onTapHandler which doesn't use the delegate pattern and still keeps the cell reusable:
class PlaceCell: UICollectionViewCell {
// A handler called when the button is pressed
var onTapHandler: (() -> Void)?
#IBOutlet weak var placeName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
initEventListeners()
}
func initEventListeners() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(handlePlaceNameTouch))
}
func handlePlaceNameTouch() {
// Make sure the handler exists, else...
guard let handler = self.onTapHandler else {
return
}
// Execute handler
handler()
}
}
You would then use it like so:
extension MyController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, PlaceCellDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlaceCell", for: indexPath) as! PlaceCell
cell.onTapHandler = { [weak self] in
self?.navigationController.pushViewController(PlaceDetailController(), animated: true)
}
return cell
}
}

Related

Collection View inside Table View Cell not being called

I am trying to implement a collection view inside a table view cell.
My table view cell is a xib, and I've dragged a collection view into it.
Then, I created a class and xib for the collection view cell:
class MyCollectionViewCell: UICollectionViewCell {
var media: Image?
#IBOutlet weak var imageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func initialize(media: PostImage) {
self.media = media
if let url = media.url {
imageView.kf.setImage(with: URL(string: url))
}
}
}
And I've given the xib the class "MyCollectionViewCell" and also given it the identifier "MyCollectionViewCell".
Then, in my table view cell class, I have done the following:
class MyTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
var post: Post!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var mediaCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
mediaCollectionView.delegate = self
mediaCollectionView.dataSource = self
let mediaCollectionViewCell = UINib(nibName: "MyCollectionViewCell", bundle: nil)
mediaCollectionView.register(mediaCollectionViewCell, forCellWithReuseIdentifier: "MyCollectionViewCell")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath as IndexPath) as? MyCollectionViewCell else {
fatalError("The dequeued cell is not an instance of MyCollectionViewCell.")
}
let media = post.images[indexPath.row]
cell.initialize(media: media)
return cell
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func initialize(post: Post) {
self.post = post
title.text = post.title
self.mediaCollectionView.reloadData()
}
}
The problem is, the collection view never shows when I run this. The title label text shows fine, but the collection view does not show, and I don't know what I'm doing wrong.
cellForItemAt doesn't even seem to get called, because when I add print("hello") at the top of the function, it never shows up in the console.
What am I doing wrong?
I think the problem is the height of the collection view is very small that it isn't shown.
Try to set the height for the table view cell:
func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat {
return 100
}
where the 100 should be bigger than the collection view

Use CollectionView methods from another swift file

I'd like to use a CollectionView methods from another swift file instead of it's ViewController for some reason.
I have this in my ViewController:
#IBOutlet weak var collectionView: UICollectionView!
var broadcastColletionView = BroadcastCollectionView()
override func viewDidLoad() {
super.viewDidLoad()
broadcastColletionView = BroadcastCollectionView(eventItems: eventItems,collectionView: collectionView, broadastObject: broadastObject)
collectionView.dataSource = broadcastColletionView
collectionView.delegate = broadcastColletionView
}
And I have BroadcastCollectionView.swift which contains the CollectionView delegate methods:
class BroadcastCollectionView: NSObject,UICollectionViewDelegate, UICollectionViewDataSource {
var eventItems = [Eventtype]()
var alreadyChecked: Bool = false
var cellHistory: IndexPath = []
var collectionView: UICollectionView!
var broadastObject = Broadcast()
init(eventItems: [Eventtype],collectionView: UICollectionView,
broadastObject: Broadcast) {
self.eventItems = eventItems
self.collectionView = collectionView
self.broadastObject = broadastObject
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return eventItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "brCollectionView", for: indexPath) as! BroadcastCollectionViewCell
self.collectionView = collectionView
cell.eventImage.image = eventItems[indexPath.row].image
cell.eventType = eventItems[indexPath.row]
let tap = UITapGestureRecognizer(target: self, action: #selector(collectionViewTapped))
tap.numberOfTapsRequired = 1
cell.addGestureRecognizer(tap)
return cell
}
#objc func collectionViewTapped(sender: UITapGestureRecognizer) {
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell : BroadcastCollectionViewCell = collectionView.cellForItem(at: indexPath)! as! BroadcastCollectionViewCell
print("item index")
} else {
print("collection view was tapped")
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected row is",indexPath.row)
}
I don't really understand why the delegate methods not called if I setted the collectionView.delegate and dataSource to the BroadcastCollactionViewclass. Please don't make me explain why would I like to separate this CollectionView it's not part of the question.
After setting datasource and delegate for the collection view in viewDidLoad() method, reload the collection view:
collectionView.reloadData()
This another class that you created is called CustomDataSource you can find a tutorial here
Try calling
collectionView.reloadData() after setting the dataSource and delegate.
and Make sure eventItems is not empty.
Hope it Helps!
Apart from the issue of not working in your case which could be due to not configuring the collection view properly which can be resolved using the answers you got above from other users, There is an approach for your question of "Use CollectionView methods from another swift file"
You can make use of the concept called "extension", I will explain you how.
Create a new class for handling the collection view methods as follows,
class MyDataSource: NSObject {
var eventItems: Array<Any>?
init(events: Array<Any>?) {
self.eventItems = events
}
}
extension MyDataSource: UITableViewDataSource, UITableViewDelegate {
// MARK: - Table view data source methods
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier")
return cell
}
// Add additional data source methods & delegate methods as per your need
// MARK: - Table view delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Perform your action
// Use delegate methods(protocols) to do your actions from the viewController class
}
}
And in the viewController file assign the datasource & delegate method for collection view as follows,
class ViewController: UIViewController {
var datasource: MyDataSource?
override func viewDidLoad() {
super.viewDidLoad()
// Initialize datasource & delegate for collectionView with the extension datasource
datasource = MyDataSource(events: EVENTITEMS_ARRAY)
collectionView?.dataSource = datasource
collectionView?.delegate = datasource
}
}
NOTE
Update the data types & collection view properties like cell identifier as per your need.

How to call collectionView.reloadData() from reusable header button?

I have a button in my reusable header view that deletes information in the array that supplies the data for the collection view and header. I want it to also be able to perform collectionView.reloadData().
The issue is that I can't call collectionView.reloadData() from the header button because it doesn't recognize collectionView as a variable. If I call Builds().collectionView.reloadData() (Builds is the View controller) the app crashes because it says that it found nil while unwrapping optional. I know that simply calling collectionView.reloadData() isn't the problem because i have collectionView.reloadData() called in viewDidAppear() and that gives me no crashes.
Whats going on here? How can I get my collectionView to reload data after the button removes the data?
For reference:
Reusable Header:
import UIKit
class BuildsHeader: UICollectionReusableView {
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//Builds().collectionBuild.reloadData() runs just fine without this line
}
}
ViewController:
import UIKit
class Builds: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionBuild: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionBuild.delegate = self
collectionBuild.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
collectionBuild.reloadData()
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("BuildCell", forIndexPath: indexPath) as? TalentsCell {
let build = heroForDetails.builds[indexPath.section]
switch (indexPath.row) {
case 0:
cell.configureCell(build["1"]! as! Talent)
case 1:
cell.configureCell(build["4"]! as! Talent)
case 2:
cell.configureCell(build["7"]! as! Talent)
case 3:
cell.configureCell(build["10"]! as! Talent)
case 4:
cell.configureCell(build["13"]! as! Talent)
case 5:
cell.configureCell(build["16"]! as! Talent)
case 6:
cell.configureCell(build["20"]! as! Talent)
default:
cell.configureCell(build["1"]! as! Talent)
}
return cell
}
return UICollectionViewCell()
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return heroForDetails.builds.count
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(50,50)
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
var buildDict = heroForDetails.builds[indexPath.section]
header.headerLabel.text = buildDict["name"] as! String
header.deleteBuildButton.tag = indexPath.section
return header
}
}
You can achieve that with delegation.
Create a protocol
Create a variable inside BuildsHeader for delegate
Call the delegate method in deleteBuildButton function
Now the code for BuildsHeader should look like this:
import UIKit
//**** The Protocol ****
protocol BuildsHeaderDelegate {
func updateCollectionView()
}
class BuildsHeader: UICollectionReusableView {
//**** Variable for the delegate ****
var delegate: BuildsHeaderDelegate?
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//**** Call the delegate method ****
self.delegate?.updateCollectionView()
}
}
In the viewForSupplementaryElementOfKind method of your collection view configure the delegate property of the header, like this:
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
//**** Set this view controller to be the header's delegate ****
header.delegate = self
// rest of header setup
Make the Builds ViewController confirm to BuildsHeaderDelegate :
class Builds: UIViewController, BuildsHeaderDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
Implement BuildsHeaderDelegate's delegate method in the Builds view controller. you can put this right after your viewForSupplementaryElementOfKind method:
func updateCollectionView () {
//**** reload the collectionView ****
self.collectionBuild.reloadData()
}
Use Delegate to call the method:
protocol BuildsHeaderDelegate(){
func reloadCollectionView()
}
class BuildsHeader: UICollectionReusableView {
var delegate:BuildsHeaderDelegate!
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//##Call delegate method##
delegate.reloadCollectionView()
}
}
Conform delegate in View Controller and Assign it:
class Builds: UIViewController,delegate:BuildsHeaderDelegate , UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionBuild: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionBuild.delegate = self
collectionBuild.dataSource = self
}
func reloadCollectionView() {
collectionBuild.reloadData()
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
//##Assign Delegate##
header.delegate = self
var buildDict = heroForDetails.builds[indexPath.section]
header.headerLabel.text = buildDict["name"] as! String
header.deleteBuildButton.tag = indexPath.section
return header
}
}

How to use collection view buttons to select view controllers in swift

I have a CollectionViewController with 3 buttons populated. The view works well, but how do I select different view controllers based on the button selected? I added the button as an action, but I don't know how to specify which button is selected so I can send the user to different viewcontrollers.
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
var imageArray = [UIImage(named: "tempOwl.png"), UIImage(named: "tempPuzzle.png"), UIImage(named: "tempHouse.png")]
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
#IBAction func menuButton(sender: UIButton) {
let controller = storyboard?.instantiateViewControllerWithIdentifier("myHome")
presentViewController(controller!, animated: true, completion: nil)
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as UICollectionViewCell
let imageView = cell.viewWithTag(1) as! UIButton
imageView.setBackgroundImage(self.imageArray[indexPath.row], forState: .Normal)
return cell
}
}
The button action method for your button in cell
func buttonAction(sender : UIButton) {
var selectedButtonCell = sender.superview as! UICollectionViewCell
//Incase your button is inside cell.contentview
// var selectedButtonCell = sender.superview.superview as! UICollectionviewCell
var indexPath = collectionView.indexPathForCell(selectedButtonCell)
if indexPath.row == 0 {
//Button in first cell is selected
//Send user to first button view controller
}
}
I suggest to create a class of type UICollectionViewCell and create the IBAction in the cell.
And set the collectionViewController as delegate. So you can pass values.
So you can create a function in the collection view controller and give it a param like a int 1 for cell 1.
If you have multiple buttons in the cell you should implement a delegate. Then, set your view controller as a delegate of the cell. Sample implementation of such a delegate could look like:
protocol YourCustomCellDelegate {
func firstButtonPressed(cell: YourCustomCell)
func secondButtonPressed(cell: YourCustomCell)
}
class YourCustomCell : UICollectionViewCell {
var delegate:YourCustomCellDelegate?
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
#IBAction func firstButtonTapped(sender: AnyObject) {
delegate?.firstButtonPressed(self)
}
#IBAction func secondButtonTapped(sender: AnyObject) {
delegate?.secondButtonPressed(self)
}
}

UICollectionViewCell not receiving tap event, even with cancelsTouchesInView set to false

I have done some research into this because I have noticed that my custom cells are not receiving the tap event. I have an NSLog that is in place to debug when the cell has been tapped. My problem though is that the cell only receives the event on a long press. When I did some more research I found that creating a reference to the UITapGestureRecognizer and setting cancelsTouchesInView to false. After doing this it still does not receive the event except on a long press. The only UITapGestureRecognizer in the view is one set so that when the user taps outside of the menu it dismisses the menu. Have I done something incorrectly? I mean for a good UX the user shouldn't need to hold the menu option for 4-5 seconds. Here is the code:
class MenuViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource
{
#IBOutlet var menuCollectionView: MenuCollectionView!
#IBOutlet var profileViewController: ProfileViewController!
#IBOutlet var menuTapGestureRecognizer: MenuTapGestureRecognizer!
#IBAction func handleTap(sender: UITapGestureRecognizer)
{
if (sender.state == .Ended)
{
self.dismissViewControllerAnimated(true, completion: nil)
}
}
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view
self.menuCollectionView.dataSource = self
self.menuCollectionView.delegate = self
menuTapGestureRecognizer.cancelsTouchesInView = false
}
// Variables
var menuImagesArray = ["MyProfileIcon.png"]
// Data Source Protocol
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return menuImagesArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
var menuCell: MenuCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("MenuCell", forIndexPath: indexPath) as MenuCollectionViewCell
var image: UIImage! = UIImage(named: menuImagesArray[indexPath.row])
menuCell.imageView.image = image
return menuCell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
NSLog("Pressed Cell")
}
}

Resources