Swipe between filtered images

懵懂的女人 提交于 2019-11-28 09:13:38

You should only need 2 image views (the current one and the incoming one, as this is a paginated style scroll), and they switch role after each filter change. And your approach of using a layer mask should work, but not on a scroll view.

So, ensure that your view organisation is something like:

UIView // receives all gestures
    UIScrollView // handles the filter name display, touch disabled
    UIImageView // incoming in front, but masked out
    UIImageView // current behind

Each image view has a mask layer, it's just a simple layer, and you modify the position of the mask layer to change how much of the image is actually visible.

Now, the main view handles the pan gesture, and uses the translation of the gesture to change the incoming image view mask layer position and the scroll view content offset.

As a change completes, the 'current' image view can't be seen any more and the 'incoming' image view takes the whole screen. The 'current' image view now gets moved to the front and becomes the incoming view, its mask gets updated to make it transparent. As the next gesture starts, its image is updated to the next filter and the change process starts over.

You can always be preparing the filtered images in the background as the scrolling is happening so that the image is ready to push into the view as you switch over (for rapid scrolling).

Aggressor

On my first attempt made a mistake in trying to mask a UIImage instead of a UIImage view, but eventually got a pretty decent working solution (which uses a UIImageView mask) below. If you have questions feel free to ask.

I basically create the current image and the filtered image. I mask the UIView (with a rectangle) and then adjust the mask based on the swipe.

Link to result: https://www.youtube.com/watch?v=k75nqVsPggY&list=UUIctdpq1Pzujc0u0ixMSeVw

Mask credit: https://stackoverflow.com/a/11391478/3324388

@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

Using Aggressor's solution partly, I came up with what I see as the simplest way to set it up, with the least lines of code.

@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))
}

Then just set the images, and make sure you have the scrollView above the imageViews. For behaviour as requested (like snapchat), make sure the scroll view has paging enabled set to true, and make sure its background colour is clear. The benefit of this method is you get all the scrollView behaviour for free... because you use a scrollView

you can create swipable filters with scroll view which carries paging scrolling of CIImage.

or

You can use this: https://github.com/pauljeannot/SnapSliderFilters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!