Проведите между отфильтрованными изображениями
Я пытаюсь разрешить пользователям пролистывать фильтры между статическими изображениями. Идея состоит в том, что изображение остается на месте, пока фильтр прокручивается над ним. Snapchat недавно выпустил версию, которая реализует эту функцию. Это видео показывает именно то, что я пытаюсь сделать в 1:05.
До сих пор я пытался поместить три UIImageView в scrollview слева и один справа от исходного изображения и настроить их кадры origin.x и size.width с помощью contentOffset.x scrollView. Я нашел эту идею в другом посте здесь. Изменение режима содержимого слева и справа на UIViewContentModeLeft и UIViewContentModeRight не помогло.
Затем я попытался наложить все три UIImageView друг на друга. Я сделал две маски CALayer и вставил их в scrollView слева и справа от стека, чтобы при прокрутке маска открывала отфильтрованное изображение. Это не сработало для меня. Любая помощь будет принята с благодарностью.
4 ответа
Вам нужно только 2 просмотра изображений (текущий и входящий, так как это прокрутка стиля с разбивкой по страницам), и они меняют роль после каждого изменения фильтра. И ваш подход к использованию маски слоя должен работать, но не в режиме прокрутки.
Итак, убедитесь, что ваша организация просмотра выглядит примерно так:
UIView // receives all gestures
UIScrollView // handles the filter name display, touch disabled
UIImageView // incoming in front, but masked out
UIImageView // current behind
Каждый вид изображения имеет слой маски, это просто простой слой, и вы изменяете положение слоя маски, чтобы изменить, насколько реально изображение видимо.
Теперь основной вид обрабатывает жест панорамирования и использует перевод этого жеста, чтобы изменить положение слоя маски входного изображения и смещение содержимого вида прокрутки.
Когда изменение завершено, "текущий" вид изображения больше не виден, а "входящий" вид изображения занимает весь экран. "Текущее" представление изображения теперь перемещается вперед и становится incoming
вид, его маска обновляется, чтобы сделать его прозрачным. Когда начинается следующий жест, его изображение обновляется до следующего фильтра, и процесс изменения начинается заново.
Вы всегда можете подготовить отфильтрованные изображения в фоновом режиме, так как происходит прокрутка, чтобы изображение было готово к переходу в вид при переключении (для быстрой прокрутки).
При первой попытке я сделал ошибку, пытаясь замаскировать UIImage вместо представления UIImage, но в итоге получил довольно приличное рабочее решение (которое использует маску UIImageView) ниже. Если у вас есть вопросы, не стесняйтесь спрашивать.
Я в основном создаю текущее изображение и отфильтрованное изображение. Я маскирую UIView (с помощью прямоугольника), а затем корректирую маску на основе удара.
Ссылка на результат: https://www.youtube.com/watch?v=k75nqVsPggY&list=UUIctdpq1Pzujc0u0ixMSeVw
Маска кредита: /questions/33160774/prosto-zamaskirujte-uiview-pryamougolnikom/33160805#33160805
@interface FilterTestsViewController ()
@end
@implementation FilterTestsViewController
NSArray *_pictureFilters;
NSNumber* _pictureFilterIterator;
UIImage* _originalImage;
UIImage* _currentImage;
UIImage* _filterImage;
UIImageView* _uiImageViewCurrentImage;
UIImageView* _uiImageViewNewlyFilteredImage;
CGPoint _startLocation;
BOOL _directionAssigned = NO;
enum direction {LEFT,RIGHT};
enum direction _direction;
BOOL _reassignIncomingImage = YES;
- (void)viewDidLoad
{
[super viewDidLoad];
[self initializeFiltering];
}
//set it up for video feed
-(void)initializeVideoFeed
{
}
-(void)initializeFiltering
{
//create filters
_pictureFilters = @[@"CISepiaTone",@"CIColorInvert",@"CIColorCube",@"CIFalseColor",@"CIPhotoEffectNoir"];
_pictureFilterIterator = 0;
//create initial image and current image
_originalImage = [UIImage imageNamed:@"ja.jpg"]; //creates image from file, this will result in a nil CIImage but a valid CGImage;
_currentImage = [UIImage imageNamed:@"ja.jpg"];
//create the UIImageViews for the current and filter object
_uiImageViewCurrentImage = [[UIImageView alloc] initWithImage:_currentImage]; //creates a UIImageView with the UIImage
_uiImageViewNewlyFilteredImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height)];//need to set its size to full since it doesn't have a filter yet
//add UIImageViews to view
[self.view addSubview:_uiImageViewCurrentImage]; //adds the UIImageView to view;
[self.view addSubview:_uiImageViewNewlyFilteredImage];
//add gesture
UIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRecognized:)];
[self.view addGestureRecognizer:pan];
}
-(void)swipeRecognized:(UIPanGestureRecognizer *)swipe
{
CGFloat distance = 0;
CGPoint stopLocation;
if (swipe.state == UIGestureRecognizerStateBegan)
{
_directionAssigned = NO;
_startLocation = [swipe locationInView:self.view];
}else
{
stopLocation = [swipe locationInView:self.view];
CGFloat dx = stopLocation.x - _startLocation.x;
CGFloat dy = stopLocation.y - _startLocation.y;
distance = sqrt(dx*dx + dy*dy);
}
if(swipe.state == UIGestureRecognizerStateEnded)
{
if(_direction == LEFT && (([UIScreen mainScreen].bounds.size.width - _startLocation.x) + distance) > [UIScreen mainScreen].bounds.size.width/2)
{
[self reassignCurrentImage];
}else if(_direction == RIGHT && _startLocation.x + distance > [UIScreen mainScreen].bounds.size.width/2)
{
[self reassignCurrentImage];
}else
{
//since no filter applied roll it back
if(_direction == LEFT)
{
_pictureFilterIterator = [NSNumber numberWithInt:[_pictureFilterIterator intValue]-1];
}else
{
_pictureFilterIterator = [NSNumber numberWithInt:[_pictureFilterIterator intValue]+1];
}
}
[self clearIncomingImage];
_reassignIncomingImage = YES;
return;
}
CGPoint velocity = [swipe velocityInView:self.view];
if(velocity.x > 0)//right
{
if(!_directionAssigned)
{
_directionAssigned = YES;
_direction = RIGHT;
}
if(_reassignIncomingImage && !_filterImage)
{
_reassignIncomingImage = false;
[self reassignIncomingImageLeft:NO];
}
}
else//left
{
if(!_directionAssigned)
{
_directionAssigned = YES;
_direction = LEFT;
}
if(_reassignIncomingImage && !_filterImage)
{
_reassignIncomingImage = false;
[self reassignIncomingImageLeft:YES];
}
}
if(_direction == LEFT)
{
if(stopLocation.x > _startLocation.x -5) //adjust to avoid snapping
{
distance = -distance;
}
}else
{
if(stopLocation.x < _startLocation.x +5) //adjust to avoid snapping
{
distance = -distance;
}
}
[self slideIncomingImageDistance:distance];
}
-(void)slideIncomingImageDistance:(float)distance
{
CGRect incomingImageCrop;
if(_direction == LEFT) //start on the right side
{
incomingImageCrop = CGRectMake(_startLocation.x - distance,0, [UIScreen mainScreen].bounds.size.width - _startLocation.x + distance, [UIScreen mainScreen].bounds.size.height);
}else//start on the left side
{
incomingImageCrop = CGRectMake(0,0, _startLocation.x + distance, [UIScreen mainScreen].bounds.size.height);
}
[self applyMask:incomingImageCrop];
}
-(void)reassignCurrentImage
{
if(!_filterImage)//if you go fast this is null sometimes
{
[self reassignIncomingImageLeft:YES];
}
_uiImageViewCurrentImage.image = _filterImage;
self.view.frame = [[UIScreen mainScreen] bounds];
}
//left is forward right is back
-(void)reassignIncomingImageLeft:(BOOL)left
{
if(left == YES)
{
_pictureFilterIterator = [NSNumber numberWithInt:[_pictureFilterIterator intValue]+1];
}else
{
_pictureFilterIterator = [NSNumber numberWithInt:[_pictureFilterIterator intValue]-1];
}
NSNumber* arrayCount = [NSNumber numberWithInt:(int)_pictureFilters.count];
if([_pictureFilterIterator integerValue]>=[arrayCount integerValue])
{
_pictureFilterIterator = 0;
}
if([_pictureFilterIterator integerValue]< 0)
{
_pictureFilterIterator = [NSNumber numberWithInt:(int)_pictureFilters.count-1];
}
CIImage* ciImage = [CIImage imageWithCGImage:_originalImage.CGImage];
CIFilter* filter = [CIFilter filterWithName:_pictureFilters[[_pictureFilterIterator integerValue]] keysAndValues:kCIInputImageKey,ciImage, nil];
_filterImage = [UIImage imageWithCIImage:[filter outputImage]];
_uiImageViewNewlyFilteredImage.image = _filterImage;
CGRect maskRect = CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height);
[self applyMask:maskRect];
}
//apply mask to filter UIImageView
-(void)applyMask:(CGRect)maskRect
{
// Create a mask layer and the frame to determine what will be visible in the view.
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
// Create a path with the rectangle in it.
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
// Set the path to the mask layer.
maskLayer.path = path;
// Release the path since it's not covered by ARC.
CGPathRelease(path);
// Set the mask of the view.
_uiImageViewNewlyFilteredImage.layer.mask = maskLayer;
}
-(void)clearIncomingImage
{
_filterImage = nil;
_uiImageViewNewlyFilteredImage.image = nil;
//mask current image view fully again
[self applyMask:CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height)];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
Частично используя решение Aggressor, я придумал простой способ его настройки с наименьшим количеством строк кода.
@IBOutlet weak var topImage: UIImageView!
@IBOutlet weak var bottomImage: UIImageView!
@IBOutlet weak var scrollview: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollview.delegate=self
scrollview.contentSize=CGSizeMake(2*self.view.bounds.width, self.view.bounds.height)
applyMask(CGRectMake(self.view.bounds.width-scrollview.contentOffset.x, scrollview.contentOffset.y, scrollview.contentSize.width, scrollview.contentSize.height))
}
func applyMask(maskRect: CGRect!){
var maskLayer: CAShapeLayer = CAShapeLayer()
var path: CGPathRef = CGPathCreateWithRect(maskRect, nil)
maskLayer.path=path
topImage.layer.mask = maskLayer
}
func scrollViewDidScroll(scrollView: UIScrollView) {
println(scrollView.contentOffset.x)
applyMask(CGRectMake(self.view.bounds.width-scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.contentSize.width, scrollView.contentSize.height))
}
Затем просто установите изображения и убедитесь, что у вас есть scrollView над imageViews. Для поведения в соответствии с запросом (например, Snapchat) убедитесь, что в представлении прокрутки включена функция подкачки страниц, установленная в значение true, и убедитесь, что его фоновый цвет чистый. Преимущество этого метода в том, что вы получаете все поведение scrollView бесплатно... потому что вы используете scrollView
Вы можете создавать сменные фильтры с видом прокрутки, который выполняет прокрутку CIImage по страницам.
или же
Вы можете использовать это: https://github.com/pauljeannot/SnapSliderFilters