Как создать свой UIView в iOS 5: Представление пяти звездного рейтинга
Логотип www.it-type.ru
15 ноября 2012 в 15:51

Как создать свой UIView в iOS 5: Представление пяти звездного рейтинга

Погружение

Давайте погрузимся в создание нового проекта с шаблоном iOS\Application\Single View Application и дадим название CustomView. Выберем конечно iPhone в Device Family, отметьте Use Storyboard и Use Automatic Reference Counting и нажмите Next.

Теперь создайте новый файл из шаблона iOS\Cocoa Touch\Objective C. Задайте имя RateView для Class name и UIView для Subclass.

Следующим шагом наполним наш файл интерфейса. Замените содержимое RateView.h текстом ниже:

#import <UIKit/UIKit.h>
 
@class RateView;
 
@protocol RateViewDelegate
- (void)rateView:(RateView *)rateView ratingDidChange:(float)rating;
@end
 
@interface RateView : UIView
 
@property (strong, nonatomic) UIImage *notSelectedImage;
@property (strong, nonatomic) UIImage *halfSelectedImage;
@property (strong, nonatomic) UIImage *fullSelectedImage;
@property (assign, nonatomic) float rating;
@property (assign) BOOL editable;
@property (strong) NSMutableArray * imageViews;
@property (assign, nonatomic) int maxRating;
@property (assign) int midMargin;
@property (assign) int leftMargin;
@property (assign) CGSize minImageSize;
@property (assign) id  delegate;
 
@end

Сначала мы создаем делегата, таким образом, мы сможем сообщить контроллеру вида когда произошли изменения рейтинга. Мы могли бы это сделать и с помощью блоков или объекта/указателя , но я подумал что так будет проще.

Далее мы устанавливаем связку свойств:

Инициализация и очистка

Теперь мы добавим представленный код  для конструкции нашего класса. Заменить RateView.m следующим содержимым:

#import "RateView.h"
 
@implementation RateView
 
@synthesize notSelectedImage = _notSelectedImage;
@synthesize halfSelectedImage = _halfSelectedImage;
@synthesize fullSelectedImage = _fullSelectedImage;
@synthesize rating = _rating;
@synthesize editable = _editable;
@synthesize imageViews = _imageViews;
@synthesize maxRating = _maxRating;
@synthesize midMargin = _midMargin;
@synthesize leftMargin = _leftMargin;
@synthesize minImageSize = _minImageSize;
@synthesize delegate = _delegate;
 
- (void)baseInit {
    _notSelectedImage = nil;
    _halfSelectedImage = nil;
    _fullSelectedImage = nil;
    _rating = 0;
    _editable = NO;    
    _imageViews = [[NSMutableArray alloc] init];
    _maxRating = 5;
    _midMargin = 5;
    _leftMargin = 0;
    _minImageSize = CGSizeMake(5, 5);
    _delegate = nil;    
}
 
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self baseInit];
    }
    return self;
}
 
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self baseInit];
    }
    return self;
}
 
@end

Это все достаточно просто мы устанавливаем экземплярам переменных значения по умолчанию. Обратите внимание что мы поддерживаем оба initWithFrame и initWithCoder что бы наш контроллер представления мог добавить с помощью XIB или программно.

Обновим наше представление

Представим, что наш контроллер представления установил экземпляры переменных с изображением, рейтингом, максимальным рейтингом и т.д., и мы создали наш подвид UIImageView. Нам нужно обновить экран на основе текущего рейтинга, напишем для этого метод. Добавьте следующий метод в файл:

- (void)refresh {
    for(int i = 0; i < self.imageViews.count; ++i) {
        UIImageView *imageView = [self.imageViews objectAtIndex:i];
        if (self.rating >= i+1) {
            imageView.image = self.fullSelectedImage;
        } else if (self.rating > i) {
            imageView.image = self.halfSelectedImage;
        } else {
            imageView.image = self.notSelectedImage;
        }
    }
}

Здесь все достаточно просто – мы просто проходит циклом по нашему списку изображение и устанавливаем соответствующее изображение, основанное на рейтинге.

Планируем подвиды

Вероятно большинство важных функций в нашем файле реализации layoutSubviews. Эти функции получают вызов всякий раз, когда структура нашего представления меняется и мы должны создать рамки соответствующего размера для всех подвидов. Добавим следующую функцию:

- (void)layoutSubviews {
    [super layoutSubviews];
 
    if (self.notSelectedImage == nil) return;
 
    float desiredImageWidth = 
		(self.frame.size.width - (self.leftMargin*2) - (self.midMargin*self.imageViews.count)) / self.imageViews.count;
    float imageWidth = MAX(self.minImageSize.width, desiredImageWidth);
    float imageHeight = MAX(self.minImageSize.height, self.frame.size.height);
 
    for (int i = 0; i < self.imageViews.count; ++i) {
 
        UIImageView *imageView = [self.imageViews objectAtIndex:i];
        CGRect imageFrame = 
			CGRectMake(self.leftMargin + i*(self.midMargin+imageWidth), 0, imageWidth, imageHeight);
        imageView.frame = imageFrame;
 
    }    
 
}

Сначала идет проверка параметра notSelectedImage настроено или нет.

И если все в порядке, мы делаем простые расчеты для выяснения как установить рамки для каждого UIImageView.

Изображения располагаются что бы заполнить всю конструкцию: левый отступ, картинка №1, центральный отступ, картинка №N, левый отступ.

Таким образом мы знаем полный размер конструкции, мы можем вычесть отступы и поделить на количество изображений, что бы узнать ширину каждого UIImageViews.

Получив эту информацию, мы проходим циклом каждый кадр UIImageVIew и обновляем его.

Установка свойств

Так как мы не знаем в каком порядке контролер представления будет устанавливать наши свойства (поскольку это может происходить даже посередине экрана), мы должны быть осторожны при построении подвидов и т.д. Это один из подходов решения проблемы, если кто нибудь знает иной подход я бы не отказался узнать его!

- (void)setMaxRating:(int)maxRating {
    _maxRating = maxRating;
 
    // Remove old image views
    for(int i = 0; i < self.imageViews.count; ++i) {
        UIImageView *imageView = (UIImageView *) [self.imageViews objectAtIndex:i];
        [imageView removeFromSuperview];
    }
    [self.imageViews removeAllObjects];
 
    // Add new image views
    for(int i = 0; i < maxRating; ++i) {
        UIImageView *imageView = [[UIImageView alloc] init];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        [self.imageViews addObject:imageView];
        [self addSubview:imageView];
    }
 
    // Relayout and refresh
    [self setNeedsLayout];
    [self refresh];
}
 
- (void)setNotSelectedImage:(UIImage *)image {
    _notSelectedImage = image;
    [self refresh];
}
 
- (void)setHalfSelectedImage:(UIImage *)image {
    _halfSelectedImage = image;
    [self refresh];
}
 
- (void)setFullSelectedImage:(UIImage *)image {
    _fullSelectedImage = image;
    [self refresh];
}
 
- (void)setRating:(float)rating {
    _rating = rating;
    [self refresh];
}

Наиболее важным методом является setMaxRating, потому что он определяет, сколько UIImage подвидов у нас есть. Потому что при изменениях мы убираем все существующие представления изображений и создаем до максимального количества. Конечно как только это произойдет необходимо вызвать layoutSubviews и refresh.

Аналогичным образом когда изменяется любое из изображений или происходит изменения рейтинга, мы должны убедится, что происходит вызов метода refresh для обновления экрана.

Обнаружение прикосновения

Заключительный финальный материал обнаружение прикосновения. Добавьте в самый низ нашего файла:

- (void)handleTouchAtLocation:(CGPoint)touchLocation {
    if (!self.editable) return;
 
    int newRating = 0;
    for(int i = self.imageViews.count - 1; i >= 0; i--) {
        UIImageView *imageView = [self.imageViews objectAtIndex:i];        
        if (touchLocation.x > imageView.frame.origin.x) {
            newRating = i+1;
            break;
        }
    }
 
    self.rating = newRating;
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    [self handleTouchAtLocation:touchLocation];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    [self handleTouchAtLocation:touchLocation];
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.delegate rateView:self ratingDidChange:self.rating];
}

Наш основной код в handleTouchAtLocation, остальные методы либо вызывают либо сообщают нашим делегатам что что то изменилось.

В handleTouchAtLocation, мы проходим по нашим подвидам(в обратном направлении) и сравниваем координаты X  наших подвидов. Если координата X больше текущего подвида, мы знаем что рейтинг увеличился на единицу.

Обратите внимание данный метод не поддерживает «пол-звезды», но при желании это можно легко изменить.

Использование представления 5 звездного рейтинга.

Наконец пришло время опробовать это! Откройте MainStoryboard.storyboard и проследуйте в Editor\Canvas\Show Bounds Rectangles ( это легче делать, когда мы видим что делаем )

Перенесите UIView и UILabel из Object Library на контроллер представления, и измените размер их размер, что бы выглядело следующим образом:

Нажмите на UIView добавленный вами и перейдите в Identity Inspector. Установите класс RateView:

Теперь соединим эти источники. Вызовите Assistant editor и убедитесь что ViewController.h виден. Управляя переместите из UIView вниз между строками @interface и @end и соедините с Outlet под именем rateView.

Повторите тоже самое для label, но соединяйте с outlet вызовом statusLabel.

Мы хотим сделать контролер представления реализацией протокола RateViewDelegate, импортируйте RateViewHeader и отметьте его. На данный момент ViewController.h должен выглядеть следующим образом:

#import <UIKit/UIKit.h>
#import "RateView.h"
 
@interface ViewController : UIViewController 
 
@property (weak, nonatomic) IBOutlet RateView *rateView;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
 
@end

Теперь переключимся на ViewController.m и внесем следующие изменения:

// Замените viewDidLoad следующим:
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.rateView.notSelectedImage = [UIImage imageNamed:@"kermit_empty.png"];
    self.rateView.halfSelectedImage = [UIImage imageNamed:@"kermit_half.png"];
    self.rateView.fullSelectedImage = [UIImage imageNamed:@"kermit_full.png"];
    self.rateView.rating = 0;
    self.rateView.editable = YES;
    self.rateView.maxRating = 5;
    self.rateView.delegate = self;
}
 
// Добавте в самый низ
- (void)rateView:(RateView *)rateView ratingDidChange:(float)rating {
    self.statusLabel.text = [NSString stringWithFormat:@"Rating: %f", rating];
}

А где же изображение звезды спросите вы? Вы можете использовать копии звезд которые сделала моя любимая жена, или использовать свои собственные!

Добавте изображения в проект, скомпилируйте и запустите и если все прошло хорошо вы увидите такую картину:

Что делать дальше?

Здесь можно взять исходный код проекта из этого руководства.

Обратите внимание на то что вы можете установить любые другие параметры вида, такие как динамические настройки рейтинга или сделать его редактируемым и т.д. Не бойтесь экспериментировать или изменять его под собственные нужды.

Я не охватил частный случай рисования в нутри DrawRect для UIView. Возможно об этом я расскажу в будущих уроках.

Позвольте мне узнать как вы используете пользовательские UIVIews в своих программах!

Оригинал статьи взят отсюда How To Make a Custom UIView in iOS 5: A 5 Star Rating View



Отправить идею!

Отправить