xyk blog

最近は iOS 開発の記事が多めです。

Swift で縦横の長さが違う UIImage を 90° 毎に回転させる

検証環境:
Xcode 12
Swift 5.3

今回は縦横の長さが違う画像を 90° 回転させた画像を作成する方法について。

f:id:xyk:20201013183740g:plain

表示上、回転するだけでよい場合は UIView の transform プロパティを使うと簡単にできる。

let radians = 90 * CGFloat.pi / 180
imageView.transform = imageView.transform.rotated(by: radians) // 90°回転

アニメーション付き

UIViewPropertyAnimator.runningPropertyAnimator(
    withDuration: 0.3, delay: 0.0, options: [.curveEaseOut]) {
        self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2)
    } completion: { position in
        // do something
    }

そうではなくて、元の画像から 90° 毎回転させた画像を新たに作成したい場合には ImageContext に書き込んで作成する。

func rotateImage(_ image: UIImage, radians: CGFloat) -> UIImage {

    let rotatedSize = CGRect(origin: .zero, size: image.size)
        .applying(CGAffineTransform(rotationAngle: radians))
        .integral.size
    
    UIGraphicsBeginImageContextWithOptions(rotatedSize, false, image.scale)
    let context = UIGraphicsGetCurrentContext()!
    context.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
    context.rotate(by: radians)
    context.scaleBy(x: 1, y: -1)
    context.translateBy(x: -image.size.width / 2, y: -image.size.height / 2)
    context.draw(image.cgImage!, in: .init(origin: .zero, size: image.size))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return newImage
}

UIImage の Extension にする場合

extension UIImage {
    
    func rotated(by radians: CGFloat) -> UIImage {

        let rotatedSize = CGRect(origin: .zero, size: size)
            .applying(CGAffineTransform(rotationAngle: radians))
            .integral.size

        UIGraphicsBeginImageContextWithOptions(rotatedSize, false, scale)
        if let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage {
            context.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
            context.rotate(by: radians)
            context.scaleBy(x: 1, y: -1)
            context.translateBy(x: -size.width / 2, y: -size.height / 2)
            context.draw(cgImage, in: .init(origin: .zero, size: size))
            let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return rotatedImage ?? self
        }
        return self
    }
}

動作確認用のViewControllerサンプル。

class RotateViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView1.contentMode = .scaleAspectFit
        imageView2.contentMode = .scaleAspectFit
        
        imageView1.image = UIImage(named: "coffee")!
    }

    var angle: CGFloat = 0
    
    func rotateImage(_ image: UIImage, clockwise: Bool) -> UIImage {
        var newAngle = clockwise ? angle + 90 : angle - 90
        if newAngle <= -360 || newAngle >= 360 {
            newAngle = 0
        }
        angle = newAngle
        
        let radians = angle * CGFloat.pi / 180
        
        let rotatedSize = CGRect(origin: .zero, size: image.size)
            .applying(CGAffineTransform(rotationAngle: radians))
            .integral.size
        
        UIGraphicsBeginImageContextWithOptions(rotatedSize, false, image.scale)
        let context = UIGraphicsGetCurrentContext()!
        context.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
        context.rotate(by: radians)
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: -image.size.width / 2, y: -image.size.height / 2)
        context.draw(image.cgImage!, in: .init(origin: .zero, size: image.size))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }
    
    @IBOutlet weak var imageView1: UIImageView!
    @IBOutlet weak var imageView2: UIImageView!
    
    // 反時計回りに -90° 毎回転させる
    @IBAction func handleLeftButton(_ sender: UIButton) {
        let newImage = rotateImage(imageView1.image!, clockwise: false)
        imageView2.image = newImage
    }
    
    // 時計回りに 90° 毎回転させる
    @IBAction func handleRightButton(_ sender: UIButton) {
        let newImage = rotateImage(imageView1.image!, clockwise: true)
        imageView2.image = newImage
    }
}

参考:

Swift - UIImageをCoreGraphicsで回転させる - Qiita