programing

CAGradientLayer, 크기가 제대로 조정되지 않고 회전시 찢어짐

procenter 2021. 1. 16. 10:44
반응형

CAGradientLayer, 크기가 제대로 조정되지 않고 회전시 찢어짐


나는 멋진 그라데이션 배경을 만들고 회전 및 모달 뷰 프레젠테이션에서 크기를 조정하기 위해 사용하는 CAGradientLayers를 얻으려고하지만 공을 사용하지 않습니다.

다음은 내 문제를 보여주는 방금 만든 비디오입니다. 회전시 찢어짐을 확인하십시오.

또한이 비디오는 OS X에서 iPhone Simulator를 촬영하여 제작되었습니다. 문제를 강조하기 위해 비디오의 애니메이션 속도를 늦췄습니다.

문제 비디오 ...

다음은 내가 방금 만든 Xcode 프로젝트 (비디오에 표시된 앱의 소스)입니다. 기본적으로 설명 된대로 회전시 문제가 발생하며 특히 뷰가 모달로 표시 될 때 발생합니다.

Xcode 프로젝트, CAGradientLayer 배경으로 뷰를 모달로 표시 ...

다음을 사용하여 이해하는 것이 가치가 있습니다.

    [[self view] setBackgroundColor:[UIColor blackColor]];

전환을 좀 더 매끄럽고 덜 거슬리게 만드는 합리적인 작업을 수행하지만 현재 가로 모드에서보기를 모달로 표시 할 때 비디오를 보면 위 코드가 도움이되지 않는 이유를 알 수 있습니다.

이 문제를 해결하기 위해 할 수있는 아이디어가 있습니까?

남자


레이어 (예 : 그래디언트 레이어)를 만들 때 레이어를 관리하는보기가 없습니다 (일부보기 레이어의 하위 레이어로 추가하더라도). 이와 같은 독립형 레이어는 UIView애니메이션 시스템에 참여하지 않습니다 .

따라서 그래디언트 레이어의 프레임을 업데이트하면 레이어가 자체 기본 애니메이션 매개 변수를 사용하여 변경 사항에 애니메이션을 적용합니다. (이를 "암시 적 애니메이션"이라고합니다.) 이러한 기본 매개 변수는 인터페이스 회전에 사용되는 애니메이션 매개 변수와 일치하지 않으므로 이상한 결과를 얻습니다.

나는 당신의 프로젝트를 보지 않았지만이 코드로 문제를 재현하는 것은 사소한 일입니다.

@interface ViewController ()

@property (nonatomic, strong) CAGradientLayer *gradientLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientLayer = [CAGradientLayer layer];
    self.gradientLayer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view.layer addSublayer:self.gradientLayer];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.gradientLayer.frame = self.view.bounds;
}

@end

시뮬레이터에서 슬로우 모션이 활성화 된 상태는 다음과 같습니다.

잘못된 회전

다행히도 이것은 고치기 쉬운 문제입니다. 그라디언트 레이어를 뷰로 관리해야합니다. 레이어로 UIView사용하는 하위 클래스를 생성 하면 CAGradientLayer됩니다. 코드는 작습니다.

// GradientView.h

@interface GradientView : UIView

@property (nonatomic, strong, readonly) CAGradientLayer *layer;

@end

// GradientView.m

@implementation GradientView

@dynamic layer;

+ (Class)layerClass {
    return [CAGradientLayer class];
}

@end

그런 다음 GradientView대신 사용할 코드를 변경해야합니다 CAGradientLayer. 이제 레이어 대신 뷰를 사용하고 있으므로 자동 크기 조정 마스크를 설정하여 그래디언트 크기를 수퍼 뷰에 맞게 유지할 수 있으므로 나중에 회전을 처리하기 위해 아무것도 할 필요가 없습니다.

@interface ViewController ()

@property (nonatomic, strong) GradientView *gradientView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.gradientView = [[GradientView alloc] initWithFrame:self.view.bounds];
    self.gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.gradientView.layer.colors = @[ (__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor blackColor].CGColor ];
    [self.view addSubview:self.gradientView];
}

@end

결과는 다음과 같습니다.

좋은 회전


@rob의 대답에 대한 가장 좋은 점은 뷰가 레이어를 제어한다는 것입니다. 다음은 레이어 클래스를 적절히 재정의하고 그라디언트를 설정하는 Swift 코드입니다.

import UIKit

class GradientView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

    private func setupView() {
        autoresizingMask = [.flexibleWidth, .flexibleHeight]

        guard let theLayer = self.layer as? CAGradientLayer else {
            return;
        }

        theLayer.colors = [UIColor.whiteColor.cgColor, UIColor.lightGrayColor.cgColor]
        theLayer.locations = [0.0, 1.0]
        theLayer.frame = self.bounds
    }

    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }
}

그런 다음 원하는 위치에 두 줄로보기를 추가 할 수 있습니다.

override func viewDidLoad() {
    super.viewDidLoad()

    let gradientView = GradientView(frame: self.view.bounds)
    self.view.insertSubview(gradientView, atIndex: 0)
}

내 빠른 버전 :

import UIKit

class GradientView: UIView {

    override class func layerClass() -> AnyClass {
        return CAGradientLayer.self
    }

    func gradientWithColors(firstColor : UIColor, _ secondColor : UIColor) {

        let deviceScale = UIScreen.mainScreen().scale
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = CGRectMake(0.0, 0.0, self.frame.size.width * deviceScale, self.frame.size.height * deviceScale)
        gradientLayer.colors = [ firstColor.CGColor, secondColor.CGColor ]

        self.layer.insertSublayer(gradientLayer, atIndex: 0)
    }
}

또한 프레임 크기를 계산하기 위해 장치 배율을 사용해야했습니다. 방향 변경 (자동 레이아웃 사용) 중에 올바른 자동 크기 조정을 수행해야했습니다.

  1. Interface Builder에서 UIView를 추가하고 클래스를 GradientView (위에 표시된 클래스)로 변경했습니다.
  2. 그런 다음 콘센트 (myGradientView)를 만들었습니다.
  3. 마지막으로 뷰 컨트롤러에서 다음을 추가했습니다.

    override func viewDidLayoutSubviews() {
        self.myGradientView.gradientWithColors(UIColor.whiteColor(), UIColor.blueColor())
    }
    

그래디언트 레이어를 만들려면 최종 프레임이 필요하므로 그래디언트 뷰는 "layoutSubviews"메서드로 생성됩니다.


이 코드를 삽입하고 willAnimateRotationToInterfaceOrientation:duration:구현을 제거하면 더 좋아 보일 것 입니다.

- (void)viewWillLayoutSubviews
{
    [[[self.view.layer sublayers] objectAtIndex:0] setFrame:self.view.bounds];    
}

그러나 이것은 매우 우아하지 않습니다. 실제 애플리케이션에서는 그라디언트 뷰를 생성하기 위해 UIView를 하위 클래스로 만들어야합니다. 이 사용자 정의보기에서 그라디언트 레이어가 지원하도록 layerClass를 재정의 할 수 있습니다.

+ (Class)layerClass
{
  return [CAGradientLayer class];
}

또한 layoutSubviews뷰의 경계가 변경 될 때 처리하도록 구현 합니다.

이 배경보기를 만들 때 자동 크기 조정 마스크를 사용하여 경계가 인터페이스 회전에 따라 자동으로 조정되도록합니다.


완전한 Swift 버전. viewFrame이 뷰를 소유하는 viewController에서 설정viewDidLayoutSubviews

import UIKit

class MainView: UIView {

    let topColor = UIColor(red: 146.0/255.0, green: 141.0/255.0, blue: 171.0/255.0, alpha: 1.0).CGColor
    let bottomColor = UIColor(red: 31.0/255.0, green: 28.0/255.0, blue: 44.0/255.0, alpha: 1.0).CGColor

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupGradient()
    }

    override class func layerClass() -> AnyClass {
        return CAGradientLayer.self
    }

    var gradientLayer: CAGradientLayer {
        return layer as! CAGradientLayer
    }

    var viewFrame: CGRect! {
        didSet {
            self.bounds = viewFrame
        }
    }

    private func setupGradient() {
        gradientLayer.colors = [topColor, bottomColor]
    }
}

또 다른 빠른 버전-drawRect를 사용하지 않습니다.

class UIGradientView: UIView {
    override class func layerClass() -> AnyClass {
        return CAGradientLayer.self
    }

    var gradientLayer: CAGradientLayer {
        return layer as! CAGradientLayer
    }

    func setGradientBackground(colors: [UIColor], startPoint: CGPoint = CGPoint(x: 0.5, y: 0), endPoint: CGPoint = CGPoint(x: 0.5, y: 1)) {
        gradientLayer.startPoint = startPoint
        gradientLayer.endPoint = endPoint
        gradientLayer.colors = colors.map({ (color) -> CGColor in return color.CGColor })
    }
}

컨트롤러에서 나는 그냥 전화합니다.

gradientView.setGradientBackground([UIColor.grayColor(), UIColor.whiteColor()])

정보

  • 한 줄 솔루션으로 사용
  • 뷰에 다시 추가 할 때 그라디언트 바꾸기 (재사용 가능)
  • 자동 전환
  • 자동 제거

세부

스위프트 3.1, xCode 8.3.3

해결책

import UIKit

extension UIView {

    func addGradient(colors: [UIColor], locations: [NSNumber]) {
        addSubview(ViewWithGradient(addTo: self, colors: colors, locations: locations))
    }
}

class ViewWithGradient: UIView {

    private var gradient = CAGradientLayer()

    init(addTo parentView: UIView, colors: [UIColor], locations: [NSNumber]){

        super.init(frame: CGRect(x: 0, y: 0, width: 1, height: 2))
        restorationIdentifier = "__ViewWithGradient"

        for subView in parentView.subviews {
            if let subView = subView as? ViewWithGradient {
                if subView.restorationIdentifier == restorationIdentifier {
                    subView.removeFromSuperview()
                    break
                }
            }
        }

        let cgColors = colors.map { (color) -> CGColor in
            return color.cgColor
        }

        gradient.frame = parentView.frame
        gradient.colors = cgColors
        gradient.locations = locations
        backgroundColor = .clear

        parentView.addSubview(self)
        parentView.layer.insertSublayer(gradient, at: 0)
        parentView.backgroundColor = .clear
        autoresizingMask = [.flexibleWidth, .flexibleHeight]

        clipsToBounds = true
        parentView.layer.masksToBounds = true

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if let parentView = superview {
            gradient.frame = parentView.bounds
        }
    }

    override func removeFromSuperview() {
        super.removeFromSuperview()
        gradient.removeFromSuperlayer()
    }
}

용법

viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])

StoryBoard 사용

ViewController

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var viewWithGradient: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])
    }
}

스토리 보드

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_17555986" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uii-31-sl9">
                                <rect key="frame" x="66" y="70" width="243" height="547"/>
                                <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                            </view>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="uii-31-sl9" secondAttribute="bottom" constant="50" id="a7J-Hq-IIq"/>
                            <constraint firstAttribute="trailingMargin" secondItem="uii-31-sl9" secondAttribute="trailing" constant="50" id="i9v-hq-4tD"/>
                            <constraint firstItem="uii-31-sl9" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="50" id="wlO-83-8FY"/>
                            <constraint firstItem="uii-31-sl9" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="50" id="zb6-EH-j6p"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="viewWithGradient" destination="uii-31-sl9" id="FWB-7A-MaH"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>

프로그래밍 방식

import UIKit

class ViewController2: UIViewController {

    @IBOutlet weak var viewWithGradient: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let viewWithGradient = UIView(frame: CGRect(x: 10, y: 20, width: 30, height: 40))
        view.addSubview(viewWithGradient)


        viewWithGradient.translatesAutoresizingMaskIntoConstraints = false
        let constant:CGFloat = 50.0

        NSLayoutConstraint(item: viewWithGradient, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin
                    , multiplier: 1.0, constant: -1*constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottomMargin
            , multiplier: 1.0, constant: -1*constant).isActive = true
        NSLayoutConstraint(item: viewWithGradient, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin
            , multiplier: 1.0, constant: constant).isActive = true

        viewWithGradient.addGradient(colors: [.blue, .green, .orange], locations: [0.1, 0.3, 1.0])
    }
}

개인적으로 저는 뷰 서브 클래스 내에 모든 것을 독립적으로 유지하는 것을 선호합니다.

내 Swift 구현은 다음과 같습니다.

            import UIKit

            @IBDesignable
            class GradientBackdropView: UIView {

                @IBInspectable var startColor: UIColor=UIColor.whiteColor()
                @IBInspectable var endColor: UIColor=UIColor.whiteColor()
                @IBInspectable var intermediateColor: UIColor=UIColor.whiteColor()

                var gradientLayer: CAGradientLayer?

                // Only override drawRect: if you perform custom drawing.
                // An empty implementation adversely affects performance during animation.
                override func drawRect(rect: CGRect) {
                    // Drawing code
                    super.drawRect(rect)

                    if gradientLayer == nil {
                        self.addGradientLayer(rect: rect)
                    } else {
                        gradientLayer?.removeFromSuperlayer()
                        gradientLayer=nil
                        self.addGradientLayer(rect: rect)
                    }
                }


                override func layoutSubviews() {
                    super.layoutSubviews()

                    if gradientLayer == nil {
                        self.addGradientLayer(rect: self.bounds)
                    } else {
                        gradientLayer?.removeFromSuperlayer()
                        gradientLayer=nil
                        self.addGradientLayer(rect: self.bounds)
                    }
                }


                func addGradientLayer(rect rect:CGRect) {
                    gradientLayer=CAGradientLayer()

                    gradientLayer?.frame=self.bounds

                    gradientLayer?.colors=[startColor.CGColor,intermediateColor.CGColor,endColor.CGColor]

                    gradientLayer?.startPoint=CGPointMake(0.0, 1.0)
                    gradientLayer?.endPoint=CGPointMake(0.0, 0.0)

                    gradientLayer?.locations=[NSNumber(float: 0.1),NSNumber(float: 0.5),NSNumber(float: 1.0)]

                    self.layer.insertSublayer(gradientLayer!, atIndex: 0)

                    gradientLayer?.transform=self.layer.transform
                }
            }

참조 URL : https://stackoverflow.com/questions/17555986/cagradientlayer-not-resizing-nicely-tearing-on-rotation

반응형