UIImagePicker allowsEditing stuck in center - iphone

I have a UIImagePicker that works perfect for a type of UIImagePickerControllerSourceTypePhotoLibrary, but when I use UIImagePickerControllerSourceTypeCamera, the editing box cannot move from the center of the image. So if the image is say taller than it is wide, the user cannot move the editing box to the top square of the image.
Anyone know why this would be the case? It only happens when the source is from the camera, not the library.
Edit: Some CODE!!!
if (actionSheet.tag == 2) {
if (buttonIndex == 0) { // Camera
// Check for camera
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == YES) {
// Create image picker controller
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
// Set source to the camera
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.allowsEditing = YES;
// Delegate is self
imagePicker.delegate = self;
// Show image picker
[self presentViewController:imagePicker
animated:YES
completion:^(void) {
}];
}
}
else if (buttonIndex == 1) { // Photo Library
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == YES) {
// Create image picker controller
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
// Set source to the camera
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagePicker.allowsEditing = YES;
// Delegate is self
imagePicker.delegate = self;
// Show image picker
[self presentViewController:imagePicker
animated:YES
completion:^(void) {
}];
}
}
So as you can see, I display them the exact same, but the camera edit acts differently than the photo library edit.

Looks like this behavior is just a bug in iOS 6... Basically you cannot move the editing box, it always bounces back to the middle unless you zoom in a bit. Hopefully they fix that soon.

Thanks yycking. This extension works. Except I added the method call inside viewDidLayoutSubviews so that I don't have to call it every time I want to open image picker.
Here's the full extenstion
extension UIImagePickerController {
open override var childForStatusBarHidden: UIViewController? {
return nil
}
open override var prefersStatusBarHidden: Bool {
return true
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
fixCannotMoveEditingBox()
}
func fixCannotMoveEditingBox() {
if let cropView = cropView,
let scrollView = scrollView,
scrollView.contentOffset.y == 0 {
var top: CGFloat = 0.0
if #available(iOS 11.0, *) {
top = cropView.frame.minY + self.view.safeAreaInsets.top
} else {
// Fallback on earlier versions
top = cropView.frame.minY
}
let bottom = scrollView.frame.height - cropView.frame.height - top
scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
var offset: CGFloat = 0
if scrollView.contentSize.height > scrollView.contentSize.width {
offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
}
scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.fixCannotMoveEditingBox()
}
}
var cropView: UIView? {
return findCropView(from: self.view)
}
var scrollView: UIScrollView? {
return findScrollView(from: self.view)
}
func findCropView(from view: UIView) -> UIView? {
let width = UIScreen.main.bounds.width
let size = view.bounds.size
if width == size.height, width == size.height {
return view
}
for view in view.subviews {
if let cropView = findCropView(from: view) {
return cropView
}
}
return nil
}
func findScrollView(from view: UIView) -> UIScrollView? {
if let scrollView = view as? UIScrollView {
return scrollView
}
for view in view.subviews {
if let scrollView = findScrollView(from: view) {
return scrollView
}
}
return nil
}
}

Reset contentInset of scrollview:
extension UIImagePickerController {
func fixCannotMoveEditingBox() {
if let cropView = cropView,
let scrollView = scrollView,
scrollView.contentOffset.y == 0 {
let top = cropView.frame.minY + self.view.safeAreaInsets.top
let bottom = scrollView.frame.height - cropView.frame.height - top
scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
var offset: CGFloat = 0
if scrollView.contentSize.height > scrollView.contentSize.width {
offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
}
scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.fixCannotMoveEditingBox()
}
}
var cropView: UIView? {
return findCropView(from: self.view)
}
var scrollView: UIScrollView? {
return findScrollView(from: self.view)
}
func findCropView(from view: UIView) -> UIView? {
let width = UIScreen.main.bounds.width
let size = view.bounds.size
if width == size.height, width == size.height {
return view
}
for view in view.subviews {
if let cropView = findCropView(from: view) {
return cropView
}
}
return nil
}
func findScrollView(from view: UIView) -> UIScrollView? {
if let scrollView = view as? UIScrollView {
return scrollView
}
for view in view.subviews {
if let scrollView = findScrollView(from: view) {
return scrollView
}
}
return nil
}
}
then call it
imagePickercontroller.fixCannotMoveEditingBox()

Here is an extension I end up using that works fine on both notch and non-notch devices. And works perfectly on iOS 15!
extension UIImagePickerController {
open override var childForStatusBarHidden: UIViewController? {
return nil
}
open override var prefersStatusBarHidden: Bool {
return true
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
fixCannotMoveEditingBox()
}
private func fixCannotMoveEditingBox() {
if let cropView = cropView, let scrollView = scrollView, scrollView.contentOffset.y == 0 {
let top: CGFloat = cropView.frame.minY + self.view.frame.minY
let bottom = scrollView.frame.height - cropView.frame.height - top
scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
var offset: CGFloat = 0
if scrollView.contentSize.height > scrollView.contentSize.width {
offset = 0.5 * (scrollView.contentSize.height - scrollView.contentSize.width)
}
scrollView.contentOffset = CGPoint(x: 0, y: -top + offset)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.fixCannotMoveEditingBox()
}
}
private var cropView: UIView? {
return findCropView(from: self.view)
}
private var scrollView: UIScrollView? {
return findScrollView(from: self.view)
}
private func findCropView(from view: UIView) -> UIView? {
let width = UIScreen.main.bounds.width
let size = view.bounds.size
if width == size.height, width == size.height {
return view
}
for view in view.subviews {
if let cropView = findCropView(from: view) {
return cropView
}
}
return nil
}
private func findScrollView(from view: UIView) -> UIScrollView? {
if let scrollView = view as? UIScrollView {
return scrollView
}
for view in view.subviews {
if let scrollView = findScrollView(from: view) {
return scrollView
}
}
return nil
}
}
p.s.: the only changes from other similar answers are:
dropping support for an old iOS version (like iOS 11);
a bit different way of calculating contentInset top property;

This is the default behavior the Image Picker Controller, you can not change it. The only other option is to create your own cropping utility. Check out the link below for an example:
https://github.com/ardalahmet/SSPhotoCropperViewController

I know, this is not a good solution, but it works.
I tested on iOS8+iPhone5, iOS9+iPhone6sPlus, iOS10+iPhone6, iOS10+iPhone6sPlus.
CAUTION: PLImageScrollView and PLCropOverlayCropView are UNDOCUMENTED classes.
- (void)showImagePickerControllerWithSourceType:(UIImagePickerControllerSourceType)sourceType {
UIImagePickerController *imagePickerController = [UIImagePickerController new];
imagePickerController.sourceType = sourceType;
imagePickerController.mediaTypes = #[(NSString *)kUTTypeImage];
imagePickerController.allowsEditing = YES;
imagePickerController.delegate = self;
[self presentViewController:imagePickerController animated:YES completion:^{
[self fxxxImagePickerController:imagePickerController];
}];
}
- (void)fxxxImagePickerController:(UIImagePickerController *)imagePickerController {
if (!imagePickerController
|| !imagePickerController.allowsEditing
|| imagePickerController.sourceType != UIImagePickerControllerSourceTypeCamera) {
return;
}
// !!!: UNDOCUMENTED CLASS
Class ScrollViewClass = NSClassFromString(#"PLImageScrollView");
Class CropViewClass = NSClassFromString(#"PLCropOverlayCropView");
[imagePickerController.view eachSubview:^BOOL(UIView *subview, NSInteger depth) {
if ([subview isKindOfClass:CropViewClass]) {
// 0. crop rect position
subview.frame = subview.superview.bounds;
}
else if ([subview isKindOfClass:[UIScrollView class]]
&& [subview isKindOfClass:ScrollViewClass]) {
BOOL isNewImageScrollView = !self->_imageScrollView;
self->_imageScrollView = (UIScrollView *)subview;
// 1. enable scrolling
CGSize size = self->_imageScrollView.frame.size;
CGFloat inset = ABS(size.width - size.height) / 2;
self->_imageScrollView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
// 2. centering image by default
if (isNewImageScrollView) {
CGSize contentSize = self->_imageScrollView.contentSize;
if (contentSize.height > contentSize.width) {
CGFloat offset = round((contentSize.height - contentSize.width) / 2 - inset);
self->_imageScrollView.contentOffset = CGPointMake(self->_imageScrollView.contentOffset.x, offset);
}
}
}
return YES;
}];
// prevent re-layout, maybe not necessary
#weakify(self, imagePickerController);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
#strongify(self, imagePickerController);
[self fxxxImagePickerController:imagePickerController];
});
}
EDIT: The eachSubview: method traverses all the subviews tree.

If you have set "View controller-based status bar appearance" to NO in info.plist and set status bar appearance as light using
UIApplication.shared.statusBarStyle = .lightContent
or using any other method , Then simply set the style as .default before presenting the image picker. for Eg:
imagePicker.allowsEditing = true
imagePicker.sourceType = .photoLibrary
UIApplication.shared.statusBarStyle = .default
present(imagePicker, animated: true, completion: nil)
Change the source type according to your need either as photoLibrary or camera and in completion block of your didFinishPickingMediaWithInfo add the following to completion block.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
//let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
var pickedImage : UIImage?
if let img = info[UIImagePickerControllerEditedImage] as? UIImage
{
pickedImage = img
}
else if let img = info[UIImagePickerControllerOriginalImage] as? UIImage
{
pickedImage = img
}
dismiss(animated: true, completion: {
UIApplication.shared.statusBarStyle = .lightContent
})}
Apparently this is a workaround for the same.Hope this helps.

A workaround that solved it is to add an entry in info.plist with "View controller-based status bar appearance" set to NO

Related

how to pinch zoom in on code created scrollview

My code creates a scrollview and image view that displays a picture array from a previous view controller. However, I am trying to implement code to make it so the user may zoom in on a picture. But what ever I do, it does not work. Any suggestions on what I am doing wrong, or where to implement the zoom in code? Thank you!
import UIKit
class DestinationVC: UIViewController {
#IBOutlet weak var myScrollView: UIScrollView!
var mySelectedProtocol:Protocol?
var pageControl:UIPageControl?
var currentPageIndex:Int=0
fileprivate var count:Int=0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if mySelectedProtocol == nil { self.navigationController?.popViewController(animated: true) }
if mySelectedProtocol!.imagesName!.count == 0 { self.navigationController?.popViewController(animated: true) }
/// We have Data
print("Img Array with Name ==> \(mySelectedProtocol?.imagesName ?? [])")
DispatchQueue.main.async {
self.addPageView()
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
myScrollView.delegate = self
myScrollView.minimumZoomScale = 1.0
myScrollView.maximumZoomScale = 5.0
return myScrollView
}
private func addPageView() {
myScrollView.backgroundColor=UIColor.black
myScrollView.isUserInteractionEnabled=true
myScrollView.showsHorizontalScrollIndicator=true
myScrollView.isPagingEnabled=true
myScrollView.delegate=self
myScrollView.bounces=false
self.count=mySelectedProtocol!.imagesName!.count
for i in 0..<self.count {
///Get Origin
let xOrigin : CGFloat = CGFloat(i) * myScrollView.frame.size.width
///Create a imageView
let imageView = UIImageView()
imageView.frame = CGRect(x: xOrigin, y: 0, width: myScrollView.frame.size.width, height: myScrollView.frame.size.height)
imageView.contentMode = .scaleAspectFit
imageView.image=UIImage(named: mySelectedProtocol!.imagesName![i])
myScrollView.addSubview(imageView)
}
setUpPageControl()
///Set Content Size to Show
myScrollView.contentSize = CGSize(width: myScrollView.frame.size.width * CGFloat(self.count), height: myScrollView.frame.size.height)
}
private func setUpPageControl() {
if pageControl == nil { pageControl=UIPageControl() }
pageControl!.numberOfPages = self.count
pageControl!.currentPageIndicatorTintColor = UIColor.red
pageControl!.pageIndicatorTintColor = UIColor.white
pageControl!.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: self.view.frame.height*0.2)
pageControl!.currentPage=currentPageIndex
self.view.addSubview(pageControl!)
self.view.bringSubview(toFront: pageControl!)
}
}
extension DestinationVC: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let scrollW : CGFloat = scrollView.frame.size.width
currentPageIndex = Int(scrollView.contentOffset.x / scrollW)
self.pageControl!.currentPage=currentPageIndex
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let scrollW : CGFloat = scrollView.frame.size.width
currentPageIndex = Int(scrollView.contentOffset.x / scrollW)
self.pageControl!.currentPage=currentPageIndex
}
}
You set the minimumScale to 1.0 - that's just 1x the normal scale. If you want it to be zoom down closer, you could try setting the minimum zoom like this:
let scaleWidth = scrollView.frame.size.width / scrollView.contentSize.width
let scaleHeight = scrollView.frame.size.height / scrollView.contentSize.height
let minScale = min(scaleWidth, scaleHeight)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1.0
scrollView.zoomScale = minScale
And set the maximumZoomScale to 1.0 - the size of the content.

Issue in blocking Vertical scroll on UIScrollView in Swift 4.0

I have an Image carousel in my app I use a UIScrollView to show the images inside. everything works fine, it's just that I want to know how do I block up movements in the UIScrollView
I'm trying to block the vertical scroll by doing:
scrollView.showsVerticalScrollIndicator = false
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical
everything in that works fine and it really blocks the vertical scroll
The problem is,
that I also have a timer, that moves the UIScrollView programmatically by doing:
var frame: CGRect = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageToMove)
frame.origin.y = -35
scrollView.scrollRectToVisible(frame, animated: true)
and once I block the vertical scroll,
this function to scrollReactToVisible doesn't do anything.
and I don't get any error for that.
is there a way currently to also block the scroll vertically (and allow to scroll right and left as usual) and also move the scrollview programmatically?
I'm attaching my full view controller:
class CaruselleScreenViewController: UIViewController, CaruselleScreenViewProtocol, UIScrollViewDelegate {
var myPresenter: CaruselleScreenPresenterProtocol?
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
var slides:[CaruselleTipsCard] = [];
var timer:Timer?
var currentPageMultiplayer = 0
override func viewDidLoad() {
super.viewDidLoad()
myPresenter = CaruselleScreenPresenter(controller: self)
//initlizes view
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
//delegates
scrollView.delegate = self
////blocks vertical movement
scrollView.showsVerticalScrollIndicator = false
//scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical
}
func scheduleTimer(_ timeInterval: TimeInterval){
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerCall), userInfo: nil, repeats: false)
}
#objc func timerCall(){
print("Timer executed")
currentPageMultiplayer = currentPageMultiplayer + 1
if (currentPageMultiplayer == 5) {
currentPageMultiplayer = 0
}
pageControl.currentPage = currentPageMultiplayer
scrollToPage(pageToMove: currentPageMultiplayer)
scheduleTimer(5)
}
func scrollToPage(pageToMove: Int) {
print ("new one")
var frame: CGRect = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageToMove)
frame.origin.y = -35
scrollView.scrollRectToVisible(frame, animated: true)
}
func createSlides() -> [CaruselleTipsCard] {
let slide1:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide1.mainPic.image = UIImage(named: "backlightingIllo")
//
let slide2:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide2.mainPic.image = UIImage(named: "comfortableIllo")
//
let slide3:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide3.mainPic.image = UIImage(named: "pharmacyIllo")
//
let slide4:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide4.mainPic.image = UIImage(named: "batteryIllo")
//
let slide5:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide5.mainPic.image = UIImage(named: "wiFiIllo")
return [slide1, slide2, slide3, slide4, slide5]
}
func setupSlideScrollView(slides : [CaruselleTipsCard]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
//////
/*
* default function called when view is scrolled. In order to enable callback
* when scrollview is scrolled, the below code needs to be called:
* slideScrollView.delegate = self or
*/
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x
// vertical
let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let currentVerticalOffset: CGFloat = scrollView.contentOffset.y
let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset
/*
* below code changes the background color of view on paging the scrollview
*/
// self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)
/*
* below code scales the imageview on paging the scrollview
*/
let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)
if(percentOffset.x > 0 && percentOffset.x <= 0.25) {
slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)
} else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)
} else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)
} else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "findingClinitionSugue" {
let destination = segue.destination as! FirstAvailableSearchViewController
//destination.consumer = consumer
}
if (timer != nil) {
timer?.invalidate()
}
}
// protocol functions
func initlizeSlides() {
slides = createSlides()
setupSlideScrollView(slides: slides)
}
func initlizeTimer() {
scheduleTimer(5)
}
}
The problem might be about setting the contentSize height value to 0 initally, so even though timer wants scrollView to move, it cannot do that.
Can you try replacing this line:
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0)
With:
scrollView.contentInsetAdjustmentBehavior = .never
Depending the application and functionality required within the scrollview - could you disable user interaction of the scrollview so it can still be moved programmatically?
That would just be
scrollView.isUserInteractionEnabled = false
This would of course depend on whether you need items in the scrollview to be interactive
Maybe you can subclass your UIScrollView, and override touchesBegan.
class CustomScrollView: UIScrollView {
var touchesDisabled = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touchesDisabled {
// here parse the touches, if they go in the horizontal direction, allow scrolling
// set tolerance for vertical movement
let tolerance: CGFloat = 5.0
let variance = touches.reduce(0, { Yvariation, touch in
Yvariation + abs(touch.location(in: view).y - touch.previousLocation(in: view).y)
})
if variance <= tolerance * CGFloat(touches.count) {
let Xtravelled = touches.reduce(0, { Xstep, touch in
Xstep + (touch.location(in: view).x - touch.previousLocation(in: view).x)
})
// scroll horizontally by the x component of hand gesture
var newFrame: CGRect = scrollView.frame
newFrame.origin.x += Xtravelled
self.scrollRectToVisible(frame, animated: true)
}
}
else {
super.touchesBegan(touches: touches, withEvent: event)
}
}
}
This way you can manually move the scrollview horizontally while disabling vertical movement when touchesDisabled is set true.
If I've understood you problem well, you can stop scrolling whenever you want with this
scrollView.isScrollEnabled = false
Using UIScrollViewDelegate (or KVO on scrollView's contentOffset), you can just counteract any vertical movement in the carousel. Something like this:
var oldYOffset: CGFloat ....
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let deltaY = oldYOffset - scrollView.contentOffset.y
oldYOffset = scrollView.contentOffset.y
scrollView.contentOffset.y -= deltaY
}
This offset change will not be visible to the user. You could even use this to increase the speed of the scrolling, invert the scrolling (pan left and scrollView scrolls right), or entirely lock the motion of the scrollView without touching isScrollEnabled, contentSize, etc.
This turned out to be quite an interesting problem...
While it is easy to lock UIScrollView scrolling to one axis only using the UIScrollViewDelegate, it is impossible to provide smooth scrolling while changing the scrolling programmatically (as you do with the Timer) at the same time.
Below, you will find a DirectionLockingScrollView class I just wrote that should make things easier for you. It's a UIScrollView that you can initialize either programmatically, or via the Interface Builder.
It features isHorizontalScrollingEnabled and isVerticalScrollingEnabled properties.
HOW IT WORKS INTERNALLY
It adds a second "control" UIScrollView that is identical to the main DirectionLockingScrollView and propagates to it all pan events intended for the main scroll view. Every time the "control" scroll view's bounds change, the change is propagated to the main scroll view BUT x and y are altered (based on isHorizontalScrollingEnabled and isVerticalScrollingEnabled) to disable scrolling on the requested axis.
DirectionLockingScrollView.swift
/// `UIScrollView` subclass that supports disabling scrolling on any direction
/// while allowing the other direction to be changed programmatically (via
/// `setContentOffset(_:animated)` or `scrollRectToVisible(_:animated)` or changing the
/// bounds etc.
///
/// Can be initialized programmatically or via the Interface Builder.
class DirectionLockingScrollView: UIScrollView {
var isHorizontalScrollingEnabled = true
var isVerticalScrollingEnabled = true
/// The control scrollview is added below the `DirectionLockingScrollView`
/// and is used to implement all native scrollview behaviours (such as bouncing)
/// based on user input.
///
/// It is required to be able to change the bounds of the `DirectionLockingScrollView`
/// while maintaining scrolling in only one direction and allowing for setting the contentOffset
/// (changing scrolling for any axis - even the disabled ones) programmatically.
private let _controlScrollView = UIScrollView(frame: .zero)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
installCustomScrollView()
}
override init(frame: CGRect) {
super.init(frame: frame)
installCustomScrollView()
}
override func layoutSubviews() {
super.layoutSubviews()
updateCustomScrollViewFrame()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
guard let superview = superview else {
_controlScrollView.removeFromSuperview()
return
}
superview.insertSubview(_controlScrollView, belowSubview: self)
updateCustomScrollViewFrame()
}
// MARK: - UIEvent propagation
func viewIgnoresEvents(_ view: UIView?) -> Bool {
let viewIgnoresEvents =
view == nil ||
view == self ||
!view!.isUserInteractionEnabled ||
!(view is UIControl && (view!.gestureRecognizers ?? []).count == 0)
return viewIgnoresEvents
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if viewIgnoresEvents(view) {
return _controlScrollView
}
return view
}
// MARK: - Main scrollview settings propagation to `controlScrollView`
override var contentInset: UIEdgeInsets {
didSet {
_controlScrollView.contentInset = contentInset
}
}
override var contentScaleFactor: CGFloat {
didSet {
_controlScrollView.contentScaleFactor = contentScaleFactor
}
}
override var contentSize: CGSize {
didSet {
_controlScrollView.contentSize = contentSize
}
}
override var bounces: Bool {
didSet {
_controlScrollView.bounces = bounces
}
}
override var bouncesZoom: Bool {
didSet {
_controlScrollView.bouncesZoom = bouncesZoom
}
}
}
extension DirectionLockingScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateBoundsFromCustomScrollView(scrollView)
}
}
private extension DirectionLockingScrollView {
/// Propagates `controlScrollView` bounds to the actual scrollview.
/// - Parameter scrollView: If the scrollview provided is not the `controlScrollView`
// the main scrollview bounds are not updated.
func updateBoundsFromCustomScrollView(_ scrollView: UIScrollView) {
if scrollView != _controlScrollView {
return
}
var newBounds = scrollView.bounds.origin
if !isHorizontalScrollingEnabled {
newBounds.x = self.contentOffset.x
}
if !isVerticalScrollingEnabled {
newBounds.y = self.contentOffset.y
}
bounds.origin = newBounds
}
func installCustomScrollView() {
_controlScrollView.delegate = self
_controlScrollView.contentSize = contentSize
_controlScrollView.showsVerticalScrollIndicator = false
_controlScrollView.showsHorizontalScrollIndicator = false
// The panGestureRecognizer is removed because pan gestures might be triggered
// on subviews of the scrollview which do not ignore touch events (determined
// by `viewIgnoresEvents(_ view: UIView?)`). This can happen for example
// if you tap and drag on a button inside the scroll view.
removeGestureRecognizer(panGestureRecognizer)
}
func updateCustomScrollViewFrame() {
if _controlScrollView.frame == frame { return }
_controlScrollView.frame = frame
}
}
USAGE
After you've included the above class in your app, don't forget to change your scroll view's class to DirectionLockingScrollView in your .xib or .storyboard.
Then update your code as below (only two lines changed, marked with // *****).
class CaruselleScreenViewController: UIViewController, CaruselleScreenViewProtocol, UIScrollViewDelegate {
var myPresenter: CaruselleScreenPresenterProtocol?
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: DirectionLockingScrollView! // *****
var slides:[CaruselleTipsCard] = [];
var timer:Timer?
var currentPageMultiplayer = 0
override func viewDidLoad() {
super.viewDidLoad()
myPresenter = CaruselleScreenPresenter(controller: self)
//initlizes view
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
scrollView.isHorizontalScrollingEnabled = false // *****
//delegates
scrollView.delegate = self
////blocks vertical movement
scrollView.showsVerticalScrollIndicator = false
//scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical
}
func scheduleTimer(_ timeInterval: TimeInterval){
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerCall), userInfo: nil, repeats: false)
}
#objc func timerCall(){
print("Timer executed")
currentPageMultiplayer = currentPageMultiplayer + 1
if (currentPageMultiplayer == 5) {
currentPageMultiplayer = 0
}
pageControl.currentPage = currentPageMultiplayer
scrollToPage(pageToMove: currentPageMultiplayer)
scheduleTimer(5)
}
func scrollToPage(pageToMove: Int) {
print ("new one")
var frame: CGRect = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageToMove)
frame.origin.y = -35
scrollView.scrollRectToVisible(frame, animated: true)
}
func createSlides() -> [CaruselleTipsCard] {
let slide1:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide1.mainPic.image = UIImage(named: "backlightingIllo")
//
let slide2:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide2.mainPic.image = UIImage(named: "comfortableIllo")
//
let slide3:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide3.mainPic.image = UIImage(named: "pharmacyIllo")
//
let slide4:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide4.mainPic.image = UIImage(named: "batteryIllo")
//
let slide5:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide5.mainPic.image = UIImage(named: "wiFiIllo")
return [slide1, slide2, slide3, slide4, slide5]
}
func setupSlideScrollView(slides : [CaruselleTipsCard]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
//////
/*
* default function called when view is scrolled. In order to enable callback
* when scrollview is scrolled, the below code needs to be called:
* slideScrollView.delegate = self or
*/
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x
// vertical
let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let currentVerticalOffset: CGFloat = scrollView.contentOffset.y
let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset
/*
* below code changes the background color of view on paging the scrollview
*/
// self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)
/*
* below code scales the imageview on paging the scrollview
*/
let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)
if(percentOffset.x > 0 && percentOffset.x <= 0.25) {
slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)
} else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)
} else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)
} else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "findingClinitionSugue" {
let destination = segue.destination as! FirstAvailableSearchViewController
//destination.consumer = consumer
}
if (timer != nil) {
timer?.invalidate()
}
}
// protocol functions
func initlizeSlides() {
slides = createSlides()
setupSlideScrollView(slides: slides)
}
func initlizeTimer() {
scheduleTimer(5)
}
}

Rotating image on iphone [duplicate]

Here is my code:
viewDidLoad:
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(pinch:)];
[self.canvas addGestureRecognizer:pinch];
pinch.delegate = self;
UIRotationGestureRecognizer *twoFingersRotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(pinchRotate:)];
[[self canvas] addGestureRecognizer:twoFingersRotate];
twoFingersRotate.delegate = self;
Code For Pinches and Rotates:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
-(void)pinchRotate:(UIRotationGestureRecognizer*)rotate
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
switch (rotate.state)
{
case UIGestureRecognizerStateBegan:
{
selectedImage.referenceTransform = selectedImage.transform;
break;
}
case UIGestureRecognizerStateChanged:
{
selectedImage.transform = CGAffineTransformRotate(selectedImage.referenceTransform, ([rotate rotation] * 55) * M_PI/180);
break;
}
default:
break;
}
}
-(void)pinch:(UIPinchGestureRecognizer*)pinch
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
[self itemSelected];
switch (pinch.state)
{
case UIGestureRecognizerStateBegan:
{
selectedImage.referenceTransform = selectedImage.transform;
break;
}
case UIGestureRecognizerStateChanged:
{
CGAffineTransform transform = CGAffineTransformScale(selectedImage.referenceTransform, pinch.scale, pinch.scale);
selectedImage.transform = transform;
break;
}
default:
break;
}
}
My rotation works great on its own and my scale works great on its own, but they wont work together. One always works or the other doesn't. When I implement shouldRecognizeSimultaneouslyWithGestureRecognizer the two gestures seem to fight against each other and produce poor results. What am I missing? (Yes I have implemented <UIGestureRecognizerDelegate>)
Every time pinch: is called, you just compute the transform based on the pinch recognizer's scale. Every time pinchRotate: is called, you just compute the transform based on the rotation recognizer's rotation. You never combine the scale and the rotation into one transform.
Here's an approach. Give yourself one new instance variable, _activeRecognizers:
NSMutableSet *_activeRecognizers;
Initialize it in viewDidLoad:
_activeRecognizers = [NSMutableSet set];
Use one method as the action for both recognizers:
- (IBAction)handleGesture:(UIGestureRecognizer *)recognizer
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
if (_activeRecognizers.count == 0)
selectedImage.referenceTransform = selectedImage.transform;
[_activeRecognizers addObject:recognizer];
break;
case UIGestureRecognizerStateEnded:
selectedImage.referenceTransform = [self applyRecognizer:recognizer toTransform:selectedImage.referenceTransform];
[_activeRecognizers removeObject:recognizer];
break;
case UIGestureRecognizerStateChanged: {
CGAffineTransform transform = selectedImage.referenceTransform;
for (UIGestureRecognizer *recognizer in _activeRecognizers)
transform = [self applyRecognizer:recognizer toTransform:transform];
selectedImage.transform = transform;
break;
}
default:
break;
}
}
You'll need this helper method:
- (CGAffineTransform)applyRecognizer:(UIGestureRecognizer *)recognizer toTransform:(CGAffineTransform)transform
{
if ([recognizer respondsToSelector:#selector(rotation)])
return CGAffineTransformRotate(transform, [(UIRotationGestureRecognizer *)recognizer rotation]);
else if ([recognizer respondsToSelector:#selector(scale)]) {
CGFloat scale = [(UIPinchGestureRecognizer *)recognizer scale];
return CGAffineTransformScale(transform, scale, scale);
}
else
return transform;
}
This works if you're just allowing rotating and scaling. (I even tested it!)
If you want to add panning, use a separate action method and just adjust selectedImage.center. Trying to do panning with rotation and scaling using selectedImage.transform is much more complicated.
Swift 3 with Pan, Rotate and Pinch
// MARK: - Gesturies
func transformUsingRecognizer(_ recognizer: UIGestureRecognizer, transform: CGAffineTransform) -> CGAffineTransform {
if let rotateRecognizer = recognizer as? UIRotationGestureRecognizer {
return transform.rotated(by: rotateRecognizer.rotation)
}
if let pinchRecognizer = recognizer as? UIPinchGestureRecognizer {
let scale = pinchRecognizer.scale
return transform.scaledBy(x: scale, y: scale)
}
if let panRecognizer = recognizer as? UIPanGestureRecognizer {
let deltaX = panRecognizer.translation(in: imageView).x
let deltaY = panRecognizer.translation(in: imageView).y
return transform.translatedBy(x: deltaX, y: deltaY)
}
return transform
}
var initialTransform: CGAffineTransform?
var gestures = Set<UIGestureRecognizer>(minimumCapacity: 3)
#IBAction func processTransform(_ sender: Any) {
let gesture = sender as! UIGestureRecognizer
switch gesture.state {
case .began:
if gestures.count == 0 {
initialTransform = imageView.transform
}
gestures.insert(gesture)
case .changed:
if var initial = initialTransform {
gestures.forEach({ (gesture) in
initial = transformUsingRecognizer(gesture, transform: initial)
})
imageView.transform = initial
}
case .ended:
gestures.remove(gesture)
default:
break
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
For this to happen you need to implement gesture delegate shouldRecognizeSimultaneouslyWithGestureRecognizer and put what gestures you would like to recognize simultaneously.
// ensure that the pinch and rotate gesture recognizers on a particular view can all recognize simultaneously
// prevent other gesture recognizers from recognizing simultaneously
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// if the gesture recognizers's view isn't one of our views, don't allow simultaneous recognition
if (gestureRecognizer.view != firstView && gestureRecognizer.view != secondView)
return NO;
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
// if either of the gesture recognizers is the long press, don't allow simultaneous recognition
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
This code needs to be modified to the view for which you want simultaneous gesture recognisers. The above code is what you need.
This example does not use gesture recognizers, and directly computes the transformation matrix. It also properly handles one-to-two finger transitions.
class PincherView: UIView {
override var bounds :CGRect {
willSet(newBounds) {
oldBounds = self.bounds
} didSet {
self.imageLayer.position = ┼self.bounds
self._adjustScaleForBoundsChange()
}
}
var oldBounds :CGRect
var touch₁ :UITouch?
var touch₂ :UITouch?
var p₁ :CGPoint? // point 1 in image coordiate system
var p₂ :CGPoint? // point 2 in image coordinate system
var p₁ʹ :CGPoint? // point 1 in view coordinate system
var p₂ʹ :CGPoint? // point 2 in view coordinate system
var image :UIImage? {
didSet {self._reset()}
}
var imageLayer :CALayer
var imageTransform :CGAffineTransform {
didSet {
self.backTransform = self.imageTransform.inverted()
self.imageLayer.transform = CATransform3DMakeAffineTransform(self.imageTransform)
}
}
var backTransform :CGAffineTransform
var solutionMatrix :HXMatrix?
required init?(coder aDecoder: NSCoder) {
self.oldBounds = CGRect.zero
let layer = CALayer();
self.imageLayer = layer
self.imageTransform = CGAffineTransform.identity
self.backTransform = CGAffineTransform.identity
super.init(coder: aDecoder)
self.oldBounds = self.bounds
self.isMultipleTouchEnabled = true
self.layer.addSublayer(layer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
let p = pʹ.applying(self.backTransform)
if self.touch₁ == nil {
self.touch₁ = touch
self.p₁ʹ = pʹ
self.p₁ = p
} else if self.touch₂ == nil {
self.touch₂ = touch
self.p₂ʹ = pʹ
self.p₂ = p
}
}
self.solutionMatrix = self._computeSolutionMatrix()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
if self.touch₁ == touch {
self.p₁ʹ = pʹ
} else if self.touch₂ == touch {
self.p₂ʹ = pʹ
}
}
CATransaction.begin()
CATransaction.setValue(true, forKey:kCATransactionDisableActions)
// Whether you're using 1 finger or 2 fingers
if let q₁ʹ = self.p₁ʹ, let q₂ʹ = self.p₂ʹ {
self.imageTransform = self._computeTransform(q₁ʹ, q₂ʹ)
} else if let q₁ʹ = (self.p₁ʹ != nil ? self.p₁ʹ : self.p₂ʹ) {
self.imageTransform = self._computeTransform(q₁ʹ, CGPoint(x:q₁ʹ.x + 10, y:q₁ʹ.y + 10))
}
CATransaction.commit()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if self.touch₁ == touch {
self.touch₁ = nil
self.p₁ = nil
self.p₁ʹ = nil
} else if self.touch₂ == touch {
self.touch₂ = nil
self.p₂ = nil
self.p₂ʹ = nil
}
}
self.solutionMatrix = self._computeSolutionMatrix()
}
//MARK: Private Methods
private func _reset() {
guard
let image = self.image,
let cgimage = image.cgImage else {
return
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
imageLayer.contents = cgimage;
imageLayer.bounds = r
imageLayer.position = ┼self.bounds
self.imageTransform = self._initialTransform()
}
private func _normalizeTransform() -> CGAffineTransform {
let center = ┼self.bounds
return CGAffineTransform(translationX: center.x, y: center.y)
}
private func _backNormalizeTransform() -> CGAffineTransform {
return self._normalizeTransform().inverted();
}
private func _initialTransform() -> CGAffineTransform {
guard let image = self.image, let cgimage = image.cgImage else {
return CGAffineTransform.identity;
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
let s = r.scaleIn(rect: self.bounds)
return CGAffineTransform(scaleX: s, y: s)
}
private func _adjustScaleForBoundsChange() {
guard let image = self.image, let cgimage = image.cgImage else {
return
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
let oldIdeal = r.scaleAndCenterIn(rect: self.oldBounds)
let newIdeal = r.scaleAndCenterIn(rect: self.bounds)
let s = newIdeal.height / oldIdeal.height
self.imageTransform = self.imageTransform.scaledBy(x: s, y: s)
}
private func _computeSolutionMatrix() -> HXMatrix? {
if let q₁ = self.p₁, let q₂ = self.p₂ {
return _computeSolutionMatrix(q₁, q₂)
} else if let q₁ = self.p₁, let q₁ʹ = self.p₁ʹ {
let q₂ = CGPoint(x: q₁ʹ.x + 10, y: q₁ʹ.y + 10).applying(self.backTransform)
return _computeSolutionMatrix(q₁, q₂)
} else if let q₂ = self.p₂, let q₂ʹ = self.p₂ʹ {
let q₁ = CGPoint(x: q₂ʹ.x + 10, y: q₂ʹ.y + 10).applying(self.backTransform)
return _computeSolutionMatrix(q₂, q₁)
}
return nil
}
private func _computeSolutionMatrix(_ q₁:CGPoint, _ q₂:CGPoint) -> HXMatrix {
let x₁ = Double(q₁.x)
let y₁ = Double(q₁.y)
let x₂ = Double(q₂.x)
let y₂ = Double(q₂.y)
let A = HXMatrix(rows: 4, columns: 4, values:[
x₁, -y₁, 1, 0,
y₁, x₁, 0, 1,
x₂, -y₂, 1, 0,
y₂, x₂, 0, 1
])
return A.inverse()
}
private func _computeTransform(_ q₁ʹ:CGPoint, _ q₂ʹ:CGPoint) -> CGAffineTransform {
guard let solutionMatrix = self.solutionMatrix else {
return CGAffineTransform.identity
}
let B = HXMatrix(rows: 4, columns: 1, values: [
Double(q₁ʹ.x),
Double(q₁ʹ.y),
Double(q₂ʹ.x),
Double(q₂ʹ.y)
])
let C = solutionMatrix ⋅ B
let U = CGFloat(C[0,0])
let V = CGFloat(C[1,0])
let tx = CGFloat(C[2,0])
let ty = CGFloat(C[3,0])
var t :CGAffineTransform = CGAffineTransform.identity
t.a = U; t.b = V
t.c = -V; t.d = U
t.tx = tx; t.ty = ty
return t
}
}

Customize dot with image of UIPageControl at index 0 of UIPageControl

I'm a new learner of ios programming. I have tried to search with another example and more questions at stackoverflow but it's not my goal. I want to set an image of dot at index 0 of UIPageControl as similar as iPhone search homescreen. Have any way to do it ? Please explain me with some code or another useful link.
Thanks in advance
I have found a solution for this problem. I know it is not the way but it will work till iOS 8 will be launched in the market.
Reason for Crash:
in iOS 7 [self.subViews objectAtIndex: i] returns UIView Instead of UIImageView and setImage is not the property of UIView and the app crashes. I solve my problem using this following code.
Check Whether the subview is UIView(for iOS7) or UIImageView(for iOS6 or earlier). And If it is UIView I am going to add UIImageView as subview on that view and voila its working and not crash..!!
-(void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView * dot = [self imageViewForSubview: [self.subviews objectAtIndex: i]];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
}
}
- (UIImageView *) imageViewForSubview: (UIView *) view
{
UIImageView * dot = nil;
if ([view isKindOfClass: [UIView class]])
{
for (UIView* subview in view.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
dot = (UIImageView *)subview;
break;
}
}
if (dot == nil)
{
dot = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, view.frame.size.width, view.frame.size.height)];
[view addSubview:dot];
}
}
else
{
dot = (UIImageView *) view;
}
return dot;
}
Also, to clear the images that are already there, set the tint colors for the existing indicators to transparent:
- (void)awakeFromNib
{
self.pageIndicatorTintColor = [UIColor clearColor];
self.currentPageIndicatorTintColor = [UIColor clearColor];
}
Hope this will solve ur issue for iOS 7.
Happy coding
Try this link:-
Answer with GrayPageControl:-
Is there a way to change page indicator dots color
It is really good and reliable.I also have used this code.
You might have to do some more customization as
-(void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView* dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) {
if(i==0) {
dot.image = [UIImage imageNamed:#"activesearch.png"];
} else {
dot.image = activeImage;
}
} else {
if(i==0) {
dot.image = [UIImage imageNamed:#"inactivesearch.png"];
} else {
dot.image = inactiveImage;
}
}
}
}
Simply change the UIPageControl page indicator Color with pattern Image self.pageControl.currentPageIndicatorTintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"circle"]];
The best compilation of the code for Swift 3 to replace the first icon of UIPageControl by a location marker:
import UIKit
class LocationPageControl: UIPageControl {
let locationArrow: UIImage = UIImage(named: "locationArrow")!
let pageCircle: UIImage = UIImage(named: "pageCircle")!
override var numberOfPages: Int {
didSet {
updateDots()
}
}
override var currentPage: Int {
didSet {
updateDots()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.pageIndicatorTintColor = UIColor.clear
self.currentPageIndicatorTintColor = UIColor.clear
self.clipsToBounds = false
}
func updateDots() {
var i = 0
for view in self.subviews {
var imageView = self.imageView(forSubview: view)
if imageView == nil {
if i == 0 {
imageView = UIImageView(image: locationArrow)
} else {
imageView = UIImageView(image: pageCircle)
}
imageView!.center = view.center
view.addSubview(imageView!)
view.clipsToBounds = false
}
if i == self.currentPage {
imageView!.alpha = 1.0
} else {
imageView!.alpha = 0.5
}
i += 1
}
}
fileprivate func imageView(forSubview view: UIView) -> UIImageView? {
var dot: UIImageView?
if let dotImageView = view as? UIImageView {
dot = dotImageView
} else {
for foundView in view.subviews {
if let imageView = foundView as? UIImageView {
dot = imageView
break
}
}
}
return dot
}
}
Attached images:
I've created a custom page controller that should function in mostly the same way without hacking into the internals of a UIPageControl or having a whole library for one small widget.
Just place an empty UIStackView in your storyboard and make its custom class this class below, and use numberOfPages and currentPage just like a normal UIPageControl. Set the spacing on the UIStackView to change how much space there is between the views.
Swift 4.2
/**
If adding via storyboard, you should not need to set a width and height constraint for this view,
just set a placeholder for each so autolayout doesnt complain and this view will size itself once its populated with pages at runtime
*/
class PageControl: UIStackView {
#IBInspectable var currentPageImage: UIImage = UIImage(named: "whiteCircleFilled")!
#IBInspectable var pageImage: UIImage = UIImage(named: "whiteCircleOutlined")!
/**
Sets how many page indicators will show
*/
var numberOfPages = 3 {
didSet {
layoutIndicators()
}
}
/**
Sets which page indicator will be highlighted with the **currentPageImage**
*/
var currentPage = 0 {
didSet {
setCurrentPageIndicator()
}
}
override func awakeFromNib() {
super.awakeFromNib()
axis = .horizontal
distribution = .equalSpacing
alignment = .center
layoutIndicators()
}
private func layoutIndicators() {
for i in 0..<numberOfPages {
let imageView: UIImageView
if i < arrangedSubviews.count {
imageView = arrangedSubviews[i] as! UIImageView // reuse subview if possible
} else {
imageView = UIImageView()
addArrangedSubview(imageView)
}
if i == currentPage {
imageView.image = currentPageImage
} else {
imageView.image = pageImage
}
}
// remove excess subviews if any
let subviewCount = arrangedSubviews.count
if numberOfPages < subviewCount {
for _ in numberOfPages..<subviewCount {
arrangedSubviews.last?.removeFromSuperview()
}
}
}
private func setCurrentPageIndicator() {
for i in 0..<arrangedSubviews.count {
let imageView = arrangedSubviews[i] as! UIImageView
if i == currentPage {
imageView.image = currentPageImage
} else {
imageView.image = pageImage
}
}
}
}
Works for my purposes but I make no guarantees
Update for Swift 3.0 ... you know if you are OK with accepting stated risk: "Modifying the subviews of an existing control is fragile".
import UIKit
class CustomImagePageControl: UIPageControl {
let activeImage:UIImage = UIImage(named: "SelectedPage")!
let inactiveImage:UIImage = UIImage(named: "UnselectedPage")!
override func awakeFromNib() {
super.awakeFromNib()
self.pageIndicatorTintColor = UIColor.clear
self.currentPageIndicatorTintColor = UIColor.clear
self.clipsToBounds = false
}
func updateDots() {
var i = 0
for view in self.subviews {
if let imageView = self.imageForSubview(view) {
if i == self.currentPage {
imageView.image = self.activeImage
} else {
imageView.image = self.inactiveImage
}
i = i + 1
} else {
var dotImage = self.inactiveImage
if i == self.currentPage {
dotImage = self.activeImage
}
view.clipsToBounds = false
view.addSubview(UIImageView(image:dotImage))
i = i + 1
}
}
}
fileprivate func imageForSubview(_ view:UIView) -> UIImageView? {
var dot:UIImageView?
if let dotImageView = view as? UIImageView {
dot = dotImageView
} else {
for foundView in view.subviews {
if let imageView = foundView as? UIImageView {
dot = imageView
break
}
}
}
return dot
}
}
You just need do it like this:
((UIImageView *)[[yourPageControl subviews] objectAtIndex:0]).image=[UIImage imageNamed:#"search.png"];
I think for this you need to customize whole UIPageControl. Please find more out at below links
How can i change the color of pagination dots of UIPageControl?
http://iphoneappcode.blogspot.in/2012/03/custom-uipagecontrol.html
From iOS 14 you can get and set the indicator image with these methods:
#available(iOS 14.0, *)
open func indicatorImage(forPage page: Int) -> UIImage?
#available(iOS 14.0, *)
open func setIndicatorImage(_ image: UIImage?, forPage page: Int)
From iProgrammer's answer
In case you want to hide the original dot
- (UIImageView *)imageViewForSubview:(UIView *)view {
UIImageView * dot = nil;
[view setBackgroundColor:[UIColor clearColor]]; << add this line
Following the previous answers I came up with the solution below.
Keep in mind that I had to add valueChanged listener that calls updateDots() as well in controller to handle taps made on UIPageControl
import UIKit
class PageControl: UIPageControl {
private struct Constants {
static let activeColor: UIColor = .white
static let inactiveColor: UIColor = .black
static let locationImage: UIImage = UIImage(named: "Location")!
}
// Update dots when currentPage changes
override var currentPage: Int {
didSet {
updateDots()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.pageIndicatorTintColor = .clear
self.currentPageIndicatorTintColor = .clear
}
func updateDots() {
for (index, view) in self.subviews.enumerated() {
// Layers will be redrawn, remove old.
view.layer.sublayers?.removeAll()
if index == 0 {
drawImage(view: view)
} else {
drawDot(index: index, view: view)
}
}
}
private func drawDot(index: Int, view: UIView) {
let dotLayer = CAShapeLayer()
dotLayer.path = UIBezierPath(ovalIn: view.bounds).cgPath
dotLayer.fillColor = getColor(index: index)
view.layer.addSublayer(dotLayer)
}
private func drawImage(view: UIView) {
let height = view.bounds.height * 2
let width = view.bounds.width * 2
let topMargin: CGFloat = -3.5
let maskLayer = CALayer()
maskLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
maskLayer.contents = Constants.locationImage.cgImage
maskLayer.contentsGravity = .resizeAspect
let imageLayer = CALayer()
imageLayer.frame = CGRect(x:0, y: topMargin, width: width, height: height)
imageLayer.mask = maskLayer
imageLayer.backgroundColor = getColor()
view.backgroundColor = .clear // Otherwise black background
view.layer.addSublayer(imageLayer)
}
private func getColor(index: Int? = 0) -> CGColor {
return currentPage == index ? Constants.activeColor.cgColor : Constants.inactiveColor.cgColor
}
}

iOS Pinch Scale and Two Finger Rotate at same time

Here is my code:
viewDidLoad:
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(pinch:)];
[self.canvas addGestureRecognizer:pinch];
pinch.delegate = self;
UIRotationGestureRecognizer *twoFingersRotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(pinchRotate:)];
[[self canvas] addGestureRecognizer:twoFingersRotate];
twoFingersRotate.delegate = self;
Code For Pinches and Rotates:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
-(void)pinchRotate:(UIRotationGestureRecognizer*)rotate
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
switch (rotate.state)
{
case UIGestureRecognizerStateBegan:
{
selectedImage.referenceTransform = selectedImage.transform;
break;
}
case UIGestureRecognizerStateChanged:
{
selectedImage.transform = CGAffineTransformRotate(selectedImage.referenceTransform, ([rotate rotation] * 55) * M_PI/180);
break;
}
default:
break;
}
}
-(void)pinch:(UIPinchGestureRecognizer*)pinch
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
[self itemSelected];
switch (pinch.state)
{
case UIGestureRecognizerStateBegan:
{
selectedImage.referenceTransform = selectedImage.transform;
break;
}
case UIGestureRecognizerStateChanged:
{
CGAffineTransform transform = CGAffineTransformScale(selectedImage.referenceTransform, pinch.scale, pinch.scale);
selectedImage.transform = transform;
break;
}
default:
break;
}
}
My rotation works great on its own and my scale works great on its own, but they wont work together. One always works or the other doesn't. When I implement shouldRecognizeSimultaneouslyWithGestureRecognizer the two gestures seem to fight against each other and produce poor results. What am I missing? (Yes I have implemented <UIGestureRecognizerDelegate>)
Every time pinch: is called, you just compute the transform based on the pinch recognizer's scale. Every time pinchRotate: is called, you just compute the transform based on the rotation recognizer's rotation. You never combine the scale and the rotation into one transform.
Here's an approach. Give yourself one new instance variable, _activeRecognizers:
NSMutableSet *_activeRecognizers;
Initialize it in viewDidLoad:
_activeRecognizers = [NSMutableSet set];
Use one method as the action for both recognizers:
- (IBAction)handleGesture:(UIGestureRecognizer *)recognizer
{
SMImage *selectedImage = [DataCenter sharedDataCenter].selectedImage;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
if (_activeRecognizers.count == 0)
selectedImage.referenceTransform = selectedImage.transform;
[_activeRecognizers addObject:recognizer];
break;
case UIGestureRecognizerStateEnded:
selectedImage.referenceTransform = [self applyRecognizer:recognizer toTransform:selectedImage.referenceTransform];
[_activeRecognizers removeObject:recognizer];
break;
case UIGestureRecognizerStateChanged: {
CGAffineTransform transform = selectedImage.referenceTransform;
for (UIGestureRecognizer *recognizer in _activeRecognizers)
transform = [self applyRecognizer:recognizer toTransform:transform];
selectedImage.transform = transform;
break;
}
default:
break;
}
}
You'll need this helper method:
- (CGAffineTransform)applyRecognizer:(UIGestureRecognizer *)recognizer toTransform:(CGAffineTransform)transform
{
if ([recognizer respondsToSelector:#selector(rotation)])
return CGAffineTransformRotate(transform, [(UIRotationGestureRecognizer *)recognizer rotation]);
else if ([recognizer respondsToSelector:#selector(scale)]) {
CGFloat scale = [(UIPinchGestureRecognizer *)recognizer scale];
return CGAffineTransformScale(transform, scale, scale);
}
else
return transform;
}
This works if you're just allowing rotating and scaling. (I even tested it!)
If you want to add panning, use a separate action method and just adjust selectedImage.center. Trying to do panning with rotation and scaling using selectedImage.transform is much more complicated.
Swift 3 with Pan, Rotate and Pinch
// MARK: - Gesturies
func transformUsingRecognizer(_ recognizer: UIGestureRecognizer, transform: CGAffineTransform) -> CGAffineTransform {
if let rotateRecognizer = recognizer as? UIRotationGestureRecognizer {
return transform.rotated(by: rotateRecognizer.rotation)
}
if let pinchRecognizer = recognizer as? UIPinchGestureRecognizer {
let scale = pinchRecognizer.scale
return transform.scaledBy(x: scale, y: scale)
}
if let panRecognizer = recognizer as? UIPanGestureRecognizer {
let deltaX = panRecognizer.translation(in: imageView).x
let deltaY = panRecognizer.translation(in: imageView).y
return transform.translatedBy(x: deltaX, y: deltaY)
}
return transform
}
var initialTransform: CGAffineTransform?
var gestures = Set<UIGestureRecognizer>(minimumCapacity: 3)
#IBAction func processTransform(_ sender: Any) {
let gesture = sender as! UIGestureRecognizer
switch gesture.state {
case .began:
if gestures.count == 0 {
initialTransform = imageView.transform
}
gestures.insert(gesture)
case .changed:
if var initial = initialTransform {
gestures.forEach({ (gesture) in
initial = transformUsingRecognizer(gesture, transform: initial)
})
imageView.transform = initial
}
case .ended:
gestures.remove(gesture)
default:
break
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
For this to happen you need to implement gesture delegate shouldRecognizeSimultaneouslyWithGestureRecognizer and put what gestures you would like to recognize simultaneously.
// ensure that the pinch and rotate gesture recognizers on a particular view can all recognize simultaneously
// prevent other gesture recognizers from recognizing simultaneously
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// if the gesture recognizers's view isn't one of our views, don't allow simultaneous recognition
if (gestureRecognizer.view != firstView && gestureRecognizer.view != secondView)
return NO;
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
// if either of the gesture recognizers is the long press, don't allow simultaneous recognition
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
This code needs to be modified to the view for which you want simultaneous gesture recognisers. The above code is what you need.
This example does not use gesture recognizers, and directly computes the transformation matrix. It also properly handles one-to-two finger transitions.
class PincherView: UIView {
override var bounds :CGRect {
willSet(newBounds) {
oldBounds = self.bounds
} didSet {
self.imageLayer.position = ┼self.bounds
self._adjustScaleForBoundsChange()
}
}
var oldBounds :CGRect
var touch₁ :UITouch?
var touch₂ :UITouch?
var p₁ :CGPoint? // point 1 in image coordiate system
var p₂ :CGPoint? // point 2 in image coordinate system
var p₁ʹ :CGPoint? // point 1 in view coordinate system
var p₂ʹ :CGPoint? // point 2 in view coordinate system
var image :UIImage? {
didSet {self._reset()}
}
var imageLayer :CALayer
var imageTransform :CGAffineTransform {
didSet {
self.backTransform = self.imageTransform.inverted()
self.imageLayer.transform = CATransform3DMakeAffineTransform(self.imageTransform)
}
}
var backTransform :CGAffineTransform
var solutionMatrix :HXMatrix?
required init?(coder aDecoder: NSCoder) {
self.oldBounds = CGRect.zero
let layer = CALayer();
self.imageLayer = layer
self.imageTransform = CGAffineTransform.identity
self.backTransform = CGAffineTransform.identity
super.init(coder: aDecoder)
self.oldBounds = self.bounds
self.isMultipleTouchEnabled = true
self.layer.addSublayer(layer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
let p = pʹ.applying(self.backTransform)
if self.touch₁ == nil {
self.touch₁ = touch
self.p₁ʹ = pʹ
self.p₁ = p
} else if self.touch₂ == nil {
self.touch₂ = touch
self.p₂ʹ = pʹ
self.p₂ = p
}
}
self.solutionMatrix = self._computeSolutionMatrix()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pʹ = touch.location(in: self).applying(self._backNormalizeTransform())
if self.touch₁ == touch {
self.p₁ʹ = pʹ
} else if self.touch₂ == touch {
self.p₂ʹ = pʹ
}
}
CATransaction.begin()
CATransaction.setValue(true, forKey:kCATransactionDisableActions)
// Whether you're using 1 finger or 2 fingers
if let q₁ʹ = self.p₁ʹ, let q₂ʹ = self.p₂ʹ {
self.imageTransform = self._computeTransform(q₁ʹ, q₂ʹ)
} else if let q₁ʹ = (self.p₁ʹ != nil ? self.p₁ʹ : self.p₂ʹ) {
self.imageTransform = self._computeTransform(q₁ʹ, CGPoint(x:q₁ʹ.x + 10, y:q₁ʹ.y + 10))
}
CATransaction.commit()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if self.touch₁ == touch {
self.touch₁ = nil
self.p₁ = nil
self.p₁ʹ = nil
} else if self.touch₂ == touch {
self.touch₂ = nil
self.p₂ = nil
self.p₂ʹ = nil
}
}
self.solutionMatrix = self._computeSolutionMatrix()
}
//MARK: Private Methods
private func _reset() {
guard
let image = self.image,
let cgimage = image.cgImage else {
return
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
imageLayer.contents = cgimage;
imageLayer.bounds = r
imageLayer.position = ┼self.bounds
self.imageTransform = self._initialTransform()
}
private func _normalizeTransform() -> CGAffineTransform {
let center = ┼self.bounds
return CGAffineTransform(translationX: center.x, y: center.y)
}
private func _backNormalizeTransform() -> CGAffineTransform {
return self._normalizeTransform().inverted();
}
private func _initialTransform() -> CGAffineTransform {
guard let image = self.image, let cgimage = image.cgImage else {
return CGAffineTransform.identity;
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
let s = r.scaleIn(rect: self.bounds)
return CGAffineTransform(scaleX: s, y: s)
}
private func _adjustScaleForBoundsChange() {
guard let image = self.image, let cgimage = image.cgImage else {
return
}
let r = CGRect(x:0, y:0, width:cgimage.width, height:cgimage.height)
let oldIdeal = r.scaleAndCenterIn(rect: self.oldBounds)
let newIdeal = r.scaleAndCenterIn(rect: self.bounds)
let s = newIdeal.height / oldIdeal.height
self.imageTransform = self.imageTransform.scaledBy(x: s, y: s)
}
private func _computeSolutionMatrix() -> HXMatrix? {
if let q₁ = self.p₁, let q₂ = self.p₂ {
return _computeSolutionMatrix(q₁, q₂)
} else if let q₁ = self.p₁, let q₁ʹ = self.p₁ʹ {
let q₂ = CGPoint(x: q₁ʹ.x + 10, y: q₁ʹ.y + 10).applying(self.backTransform)
return _computeSolutionMatrix(q₁, q₂)
} else if let q₂ = self.p₂, let q₂ʹ = self.p₂ʹ {
let q₁ = CGPoint(x: q₂ʹ.x + 10, y: q₂ʹ.y + 10).applying(self.backTransform)
return _computeSolutionMatrix(q₂, q₁)
}
return nil
}
private func _computeSolutionMatrix(_ q₁:CGPoint, _ q₂:CGPoint) -> HXMatrix {
let x₁ = Double(q₁.x)
let y₁ = Double(q₁.y)
let x₂ = Double(q₂.x)
let y₂ = Double(q₂.y)
let A = HXMatrix(rows: 4, columns: 4, values:[
x₁, -y₁, 1, 0,
y₁, x₁, 0, 1,
x₂, -y₂, 1, 0,
y₂, x₂, 0, 1
])
return A.inverse()
}
private func _computeTransform(_ q₁ʹ:CGPoint, _ q₂ʹ:CGPoint) -> CGAffineTransform {
guard let solutionMatrix = self.solutionMatrix else {
return CGAffineTransform.identity
}
let B = HXMatrix(rows: 4, columns: 1, values: [
Double(q₁ʹ.x),
Double(q₁ʹ.y),
Double(q₂ʹ.x),
Double(q₂ʹ.y)
])
let C = solutionMatrix ⋅ B
let U = CGFloat(C[0,0])
let V = CGFloat(C[1,0])
let tx = CGFloat(C[2,0])
let ty = CGFloat(C[3,0])
var t :CGAffineTransform = CGAffineTransform.identity
t.a = U; t.b = V
t.c = -V; t.d = U
t.tx = tx; t.ty = ty
return t
}
}