React Native custom iOS view that responds to height/width style - ios

I'm trying to get my custom UIView to respond to width and height styles, e.g.
<MyCustomView style={{width: 200, height: 300}}/>
I've closely followed the simple example at https://github.com/brentvatne/react-native-dashed-border-example
Currently my View and ViewManager look like:
#import "MyCustomView.h"
#import "RCTViewManager.h"
#implementation MyCustomView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Create the component view
}
return self;
}
#end
and the ViewManager:
#implementation MyCustomViewManager
RCT_EXPORT_MODULE();
- (UIView *)view
{
return [[MyCustomView alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
}
#end
However this of course always fills the screen. I want to be able to set the height on this component, and ideally using the RN method of passing it via style.
If I follow the simple border example, I end up with
#implementation MyCustomView
- (instancetype)init
{
self = [super init];
if (self) {
// Create the component view
}
return self;
}
#end
and
#implementation MyCustomViewManager
RCT_EXPORT_MODULE();
- (UIView *)view
{
return [[MyCustomView alloc] init];
}
#end
But the view does not show, and reports self.bounds.size.height to be 0.00.
Is there something I've missed to get this working? Do I need to employ RCTBridge? I did previously have the line
#synthesize bridge = _bridge;
as in https://github.com/brentvatne/react-native-dashed-border-example/blob/master/BVDashedBorderViewManager.m#L9 but I got an error saying "Bridge not set"

You can override reactSetFrame in your view and you'll get access to the frame whenever it's updated by a React Native layout:
- (void)reactSetFrame:(CGRect)frame
{
CGFloat height = frame.size.height;
// Do something with the height here
[super reactSetFrame: frame];
}
You'll need to import the React category for UIView if you're not already for that file:
#import "UIView+React.h"

Related

Performing selector on superView

I have an UICollectionView which uses - (void)scrollViewDidScroll:(UIScrollView *)scrollViewto determine when the user has scrolled to the bottom.
The owner of my UICollectionView has a method which sends a command to a server and asks for more data.
What I want to do is to perform a selector on the owner (my viewcontroller) from my UICollectionView, and specifically from within scrollViewDidScroll.
I try to do it using the following code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[[self superView] performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
Note: the selector onScrolledToBottom is a SEL property of my UICollectionView.
The error I am getting says:
-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00'
EDIT
I've stripped my code down to fit this question.
I have the followin in my ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self.view: flowLayout: #selector(getMoreInfo)];
}
And for my myCollectionView the .hand .mfiles look as following:
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIView *parentView;
SEL onScrolledToBottom;
}
#property UIView *parentView;
#property SEL onScrolledToBottom;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
#end
and
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentView = parentView;
#synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
parentView = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentView performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
You're getting the superview instead of the parent controller, that's one. Secondly, you shouldn't try to do it this way. Instead use a protocol and pass your controller as a delegate to your custom collectionview.
#protocol MyCollectionViewScrollProtocol <NSObject>
- (void)scrolledToBottom;
#end
Then in your ViewController implement this protocol and in your CollectionView
create a weak property delegate like this:
#property(weak, nonatomic) id<MyCollectionViewScrollProtocol> delegate;
Then you'll be able to call it
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[self.delegate scrolledToBottom];
}
}
and don't forget to set the delegate property from the controller.
Edit by OP:
The controller should contain the following code in the .hfile:
#import <UIKit/UIKit.h>
#protocol ViewControllerProtocol <NSObject>
- (void) Pong;
#end
#interface ViewController : UIViewController <ViewControllerProtocol>
#end
and the following code somewhere in the .m file:
#import "ViewController.h"
#import UIKit;
#import "myUnit.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
myUnit mU = [[myUnit alloc] init];
mU.linkToController = self;
[mU Ping];
}
......
- (void) Pong{
NSLog(#"Pong");
}
......
The unit that wants to access the controller needs to have the following code in the .hfile:
#import <Foundation/Foundation.h>
#import "ViewController.h"
#interface myUnit : NSObject {
}
#property (weak, nonatomic) id <ViewControllerProtocol> linkToController;
- (void)Ping;
#end
And the unit that wants to access the controller needs to have the following code in the .mfile:
#import "myUnit.h"
#import "ViewController.h"
#implementation myUnit
- (void) Ping {
NSLog(#"Ping");
[self.linkToController Pong];
}
#end
The collectionView.parentView will be ViewController.view, which is totally a UIView, doesn't have a method called onScrolledToBottom.
Here is a approach if I right about what you want, but IT'S REALLY REALLY A BAD WAY to do this.
in ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] initWithParentViewController:self layout:flowLayout selector:#selector(getMoreInfo)];
}
in myCollectionView.h
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIViewController *_parentViewController;
SEL _onScrolledToBottom;
}
#property(weak) UIViewController *parentViewController;
#property SEL onScrolledToBottom;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL;
#end
in myCollectionView.m
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentViewController = _parentViewController;
#synthesize onScrolledToBottom = _onScrollToBotton;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
_parentViewController = parentViewController;
self.delegate = self;
self.dataSource = self;
_onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[_parentViewController performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
WARNING:
In MVC there should be a Controller to implement
the protocol, not a view.
Class name's first character must be capitalized, you should use MyCollectionView instead of myCollectionView as a class name.
When use property, the LLVM compiler will automatically generate a member variable, no need to declare it again.
In this case use protocol-delegate is the best way.
Your "myCollectionView" should have a delegate (you will have to create a protocol for that) with some method along the line:
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView
By convention view will pass itself to this method (just like any tableView delegate methods). Then in the initCollectionView after myCollectionView *mVC = [myCollectionView alloc] init... you will have to set this delegate to the current view controller and implement that method.
mVC.delegate = self;
...
some other place in code
...
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView {
//do what's need to be done ;)
}
Now your "myCollectionView" does not care where it's just and by "who". Anyone who would like to use it can implement this method and be a delegate for that view with any custom logic. That is in this method you would call any required methods and any other things.
How this works:
View when scrolls down notifies it's delegate about this event
Delegate handles all the logic what's need to be done
:)
First I want to say thank you to all of you.
I read through everything you guys wrote and I was inspired.
Especially Stanislav Ageev's answer where he said
You're getting the superview instead of the parent controller...
.
I tried to pass the controller itself as a UIViewController instead of view of the controller.
And I also removed the #selector() from within the scrollViewDidScroll and passed the selector directly.
EDIT I missed zy.lius answer. Credit goes to him/her for posting an answer which basically explains what I've done here even though I figured this out on my own. Sorry.
My code now looks like this (.h/.m):
Controller
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self: flowLayout: #selector(getMoreInfo)];
}
myCollectionView.h file:
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UICollectionView *parentController;
SEL onScrolledToBottom;
}
#property UICollectionView *parentController;
#property SEL onScrolledToBottom;
- (id)init: (UICollectionView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
#end
and the .m file:
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentController = parentController;
#synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.view.frame.size.width, parent.view.frame.size.height) collectionViewLayout: layout];
if (self) {
parentController = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentController performSelector:#onScrolledToBottom withObject:nil];
}
}
This works perfectly fine and this could be used to communicate with the main controller in various ways.
And it is very simple.
Thanks again for inspiring me to find the most efficient solution.

Add category to UITextView and Override it's init function while UITextView is in xib

I can't intercept the init function that's getting called when it's getting created inside of the xib file.
I want to add borderline to it when it gets created so that I won't need to add it manually.
.h:
#import <UIKit/UIKit.h>
#interface UITextView (FITAddBorderline)
- (id) init;
- (id) initWithFrame:(CGRect)frame;
#end
.m:
#import "UITextView+FITAddBorderline.h"
#implementation UITextView (FITAddBorderline)
- (id) init
{
self = [super init];
if (self) {
[self addBorderline];
}
return self;
}
- (id) initWithFrame:(CGRect)frame {
self = [super init];
if (self) {
self.frame = frame;
[self addBorderline];
}
return self;
}
- (void) addBorderline {
//To make the border look very close to a UITextField
[self.layer setBorderColor:[[[UIColor grayColor] colorWithAlphaComponent:0.5] CGColor]];
[self.layer setBorderWidth:2.0];
//The rounded corner part, where you specify your view's corner radius:
self.layer.cornerRadius = 5;
self.clipsToBounds = YES;
}
#end
Views that come from NIBs are initialized with initWithCoder:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self addBorderline];
}
return self;
}
As a side note, I would recommend changing what you are doing and use a subclass instead of a category. You can get yourself into some trouble overriding methods in a category. See more info here.
You just need to implement the awakeFromNib method:
-(void)awakeFromNib
{
[super awakeFromNib];
[self addBorderline];
}

drawRect not getting called from setNeedsDisplay

I have recently started learning core graphics to draw on views, but every time I call setNeedsDisplay, the method runs but doesn't trigger drawRect. In my project, I have a view on a view controller. The view contains an if statement in drawRect that checks if a BOOL variable is YES or NO. If YES, it uses the code to draw a red circle. If NO, it draws a blue circle. In the BOOL's setter, it calls setNeedsDisplay. I create an instance of this view class in the ViewController and set the BOOL but the view is not redrawing. Why is the method not getting called? I posted the xcode files and code bits below.
Customview.m:
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
-(void)awakeFromNib{
[self setup];
}
-(void)setup{
_choice1 = YES;
}
-(void)setChoice1:(BOOL)choice1{
_choice1 = choice1;
[self setNeedsDisplay];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
if (_choice1) {
UIBezierPath * redCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor redColor]setFill];
[redCircle fill];
}else if (!_choice1){
UIBezierPath * blueCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor blueColor]setFill];
[blueCircle fill];
}
}
#end
CustomView.h:
#import <UIKit/UIKit.h>
#interface CustomView : UIView
#property (nonatomic) BOOL choice1;
#end
ViewController.m:
#import "ViewController.h"
#import "CustomView.h"
#interface ViewController ()
- (IBAction)Change:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Change:(id)sender {
CustomView * cv = [[CustomView alloc]init];
[cv setChoice1:NO];
}
#end
Full project: https://www.mediafire.com/?2c8e5uay00wci0c
Thanks
You need to create an outlet for your CustomView in your ViewController. Delete the code that creates the new CustomView instance and use _cv to refer to the customView.
#interface ViewController ()
- (IBAction)Change:(id)sender;
#property (weak,nonatomic) IBOutlet CustomView *cv;
#end
Then, in the storyboard, connect ViewController's cv outlet to the CustomView. Also, you'll need to implement the initFromCoder: method on CustomView.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self setup];
}
return self;
}

iOS setting view variable from view controller programmatically

I've created a view with a variable and loaded it in the loadView method of a view controller. How do I pass a value to the view variable from the view controller after the loadView method has been called?
LocationView.m
#import "LocationView.h"
#implementation LocationView
#synthesize locationTitle;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
locationTitle = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 300, 20)];
[self addSubview:locationTitle];
}
return self;
}
LocationViewController.m
#import "LocationViewController.h"
#import "LocationView.h"
#interface LocationViewController ()
#end
#implementation LocationViewController
- (void)loadView
{
CGRect frame = [[UIScreen mainScreen] bounds];
LocationView *locationView = [[LocationView alloc] initWithFrame:frame];
[self setView:locationView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
How do I pass a value to locationTitle here?
}
You've already set up the locationTitle property for LocationView objects, so you can just access that. The only thing you need to do beforehand is keep a reference to the LocationView object around in an instance variable so you can access it from any instance method.
#implementation LocationViewController {
LocationView *_locationView;
}
- (void)loadView
{
CGRect frame = [[UIScreen mainScreen] bounds];
_locationView = [[LocationView alloc] initWithFrame:frame];
[self setView:_locationView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_locationView.locationTitle = #"Test";
}
#end
Alternatively, since you are assigning the custom view to the view controllers main view, you could cast that:
- (void)viewDidLoad
{
[super viewDidLoad];
((LocationView *)self.view).locationTitle = #"Test";
}

Add UIViewController as subview of UIScrollView

I am building an app with some scrolling inside. In short, this is my configuration:
1) class_1: a view with a set of elements inside (something similar to camera row) listed by using a collection view
2) class_2: a modal view called by class_1 where I show each singular element selected (from class_1). Inside the same class I have also implemented the horizontal scroll of all the elements
3) class_3: a simple viewcontroller with an imageview inside which will contain the element I will show in class_2
Running my app looks like class_3 is not added as subview of class_2 and all the singular content I try to load are not visible.
Below is my class_2 code
Interface:
#import <UIKit/UIKit.h>
#import "class_3.h"
#interface class_2 : UIViewController
#property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (weak, nonatomic) NSArray *imageList;
#end
Implementation:
#interface class_2 () {
NSMutableArray *_viewControllers;
}
- (void)loadScrollViewWithImage:(NSUInteger)indexImage;
#end
#implementation class_2
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// _imageList is passed from class_1 when I call class_2. Contains an array of images
self.pageControl.numberOfPages = [_imageList count];
self.pageControl.currentPage = 0;
// I store inside an array N objects as the number f images I have to show. At the beginning the array has only null objects, then it will contains the views based on the images I have scrolled.
//I am storying my images inside a view because maybe I will show more controls and not just the image. It will be much easier manage them.
_viewControllers = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < [_imageList count]; i++) {
[_viewControllers addObject:[NSNull null]];
}
// I set some properties of my scrollview defined as outlet
self.scrollView.frame = CGRectMake(0, 0, 320, 345);
self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.scrollView.frame) * [_imageList count], CGRectGetHeight(self.scrollView.frame));
self.scrollView.pagingEnabled = YES;
// this is set to NO, I switched to YES just now for debug (at least I can see how big the area is since there are no images visible)
self.scrollView.showsHorizontalScrollIndicator = YES;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.scrollsToTop = NO;
self.scrollView.delegate = self;
// for testing now I always take the first and second elements of my array. I load both so when I move from the first image to the second is already there and there is no gap/flashing effect
[self loadScrollViewWithImage:0];
[self loadScrollViewWithImage:1];
}
#pragma mark - Private methods
- (void)loadScrollViewWithImage:(NSUInteger)indexImage {
// I scrolled until the last image, from here I can only go back
if (indexImage >= [_imageList count])
return;
// I replace the placeholder (null) object
class_3 *controller = [_viewControllers objectAtIndex:indexImage];
if ((NSNull *)controller == [NSNull null]) {
controller = [[class_3 alloc] init];
[_viewControllers replaceObjectAtIndex:indexImage withObject:controller];
}
// I add the controller's view to the scroll view - in case this is the first time I'm scrolling this image
if (controller.view.superview == nil) {
// I set my controller frame as the scrollview works as container
CGRect frame = self.scrollView.frame;
frame.origin.x = CGRectGetWidth(frame) * indexImage;
frame.origin.y = 0;
controller.view.frame = frame;
// I finally add my viewcontroller as child of my superview and subview of the scrollview
[self addChildViewController:controller];
[self.scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self];
// I set an image corrisponding with the element selected from class_1
controller.imageView.image = [_imageList objectAtIndex:indexImage]
// I also tried with a fake image...just in case. No one of them is visible
//controller.imageView.image = [UIImage imageNamed:#"tempPhoto"];
}
}
My class_3 is a viewcontroller which hosts only an image view shoulb be populated with the corresponding image selezted before.
The pagecontroller is used to show where I scrolling (letf/right) and how many elements there are. The scrolling is phisically managed with the scrollview.
Here is my class_3 definition
Interface:
#import <UIKit/UIKit.h>
#import "class_3.h"
#interface class_3 : UIViewController
#property (strong, nonatomic) IBOutlet UIImageView *imageView;
#end
Implementation (nothing really impressive)
#import "class_3.h"
#interface class_3 ()
#end
#implementation class_3
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.imageView.image = [UIImage imageNamed:#"tempPhoto"];
}
#end
What am I missing?
Tnx
Your class_3 should be a UIView, instead of a UIViewController, subclass. See here

Resources