2017-01-23 15 views
1

Я использую очень удобный UIColor(patternImage:), чтобы создать CAShapeLayer с черепичными узорами в приложении iOS 10 с Xcode 8.2. Плинтус всегда начинается в начале представления, что может быть неудобно, если вы хотите, чтобы он начинался где-то в другом месте. Чтобы проиллюстрировать это, вот скриншот от имитатора (код ниже):Как я могу изменить фазу UIColor patternImage в Swift?

Tiling problem

CAShapeLayer слева начинается в точке (0,0), так что все в порядке. Тот, что справа, находится на (110,50), поэтому он разделен посередине. Вот код:

let firstBox = CAShapeLayer() 
firstBox.fillColor = UIColor(patternImage: UIImage(named: "test-image")!).cgColor 
view.layer.addSublayer(firstBox) 
firstBox.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100)).cgPath 

let secondBox = CAShapeLayer() 
secondBox.fillColor = UIColor(patternImage: UIImage(named: "test-image")!).cgColor 
view.layer.addSublayer(secondBox) 
secondBox.path = UIBezierPath(rect: CGRect(x: 110, y: 50, width: 100, height: 100)).cgPath 

Я хочу, чтобы настроить фазу шаблона для правильного CAShapeLayer, так что обе плитки показывают анфас. Apple, documentation для UIColor (patternImage :) услужливо относимся к функции для этой цели:

Чтобы изменить фазу, сделать цвет текущего цвета, а затем использовать функцию setPatternPhase(_:) изменить фазу.

Звучит просто! Но мне трудно это реализовать. Я не совсем уверен, что означает «сделать цвет текущего цвета». Я пытался получать текущий контекст и вызов setPatternPhase на него, как до, так и после назначения цвет заливки слоя:

UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 25, height: 25)) 

Нет заметного эффекта. Я попробовал подклассификацию содержащего UIView и установил фазу в ее методе drawRect:, как предложено в this answer. Но drawRect: не существует в Swift, поэтому я попробовал как draw(_ rect:), так и draw(_ layer:, in:). Обе функции вызываются, но нет заметного эффекта.

class PatternView: UIView { 
    override func draw(_ rect: CGRect) { 
     UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 25, height: 25)) 
     super.draw(rect) 
    } 
    override func draw(_ layer: CALayer, in ctx: CGContext) { 
     ctx.setPatternPhase(CGSize(width: 25, height: 25)) 
     super.draw(layer, in: ctx) 
    } 
} 

По предложению Дейва Уэстона, я использовал UIImage «s .set() для установки текущего обводки и заливки для текущего контекста перед вызовом setPatternPhase. К сожалению, выход не изменяется. Вот код, который я попробовал:

let secondBoxColor = UIColor(patternImage: UIImage(named: "test-image")!) 
secondBoxColor.set() 
UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 50, height: 50)) 

let secondBox = CAShapeLayer() 
secondBox.fillColor = secondBoxColor.cgColor 
view.layer.addSublayer(secondBox) 
secondBox.path = UIBezierPath(rect: CGRect(x: 110, y: 50, width: 100, height: 100)).cgPath 

Как я могу сдвинуть фазу шаблона, который получает втянут в CAShapeLayer?

ответ

0

Чтобы сделать ваш шаблон текущим цветом, вы должны вызвать метод экземпляра set() на экземпляре UIColor, который содержит ваш шаблон. Это настраивает цвет как текущий ход и цвет заливки для текущего контекста.

Затем, согласно документам Apple, setPatternPhase должен работать.

+0

Спасибо за вашу помощь. Я пытался использовать 'set()' перед 'setPatternPhase()', но это не влияет на результат. Я редактировал свой вопрос, чтобы включить новый код (в конце). Что я делаю не так? – Robert

0

Я не смог решить эту проблему, но я думал, что поделюсь обходным решением, которое я использую, в случае, если это полезно для всех.

Насколько я могу судить, CAShapeLayer делает его рендеринг в секретном, скрытом месте, и игнорирует нормальные display() и draw() функции, которые CALayerDelegate s, как предполагается использовать. В результате у вас никогда не будет доступа к CGContext, который он использует для рендеринга, поэтому нет способа вызвать setPatternPhase().

Мой конкретный прецедент для setPatternPhase() должен был иметь шаблон, расположенный вверху слева от CAShapeLayer, который он нарисовал, поэтому я нашел альтернативный способ сделать это. Это не позволяет вам установить произвольную фазу.

То, что я вместо того, чтобы создать новый подкласс CALayer под названием CAPatternLayer, который принимает UIImage для плитки и CGPath для заполнения. Он делегирует класс CALayerDelegate под названием CAPatternLayerDelegate, который предоставляет функцию draw(layer: in ctx:). Когда запрашивается ничья, делегат создает временный UIImageView, заполняет его черепичным изображением, а затем отображает его в контексте CALayer.

Приятный побочный эффект заключается в том, что вы можете использовать UIImage с вставками для крышек, что позволяет масштабировать 9-срез с центральным срезом.

Вот код для PatternLayer и PatternLayerDelegate:

class CAPatternLayer: CALayer { 
    var image: UIImage? 
    var path: CGPath? { 
     didSet { 
      if let path = self.path { 
       self.frame = path.boundingBoxOfPath 
       // shift the path to 0,0 since you built position into the frame 
       var translation = CGAffineTransform(translationX: -path.boundingBoxOfPath.origin.x, y: -path.boundingBoxOfPath.origin.y) 
       let shiftedPath = path.copy(using: &translation) 
       // use the shifted version 
       self.path = shiftedPath 
       self.maskLayer.path = shiftedPath 
      } 
     } 
    } 
    let maskLayer: CAShapeLayer = CAShapeLayer() 

    override init() { 
     super.init() 
     self.delegate = CAPatternLayerDelegate.sharedInstance 
     self.setNeedsDisplay() 
     self.mask = self.maskLayer 
    } 
    required init?(coder aDecoder: NSCoder) { 
     fatalError("init(coder:) has not been implemented") 
    } 
    convenience init(path: CGPath, image: UIImage) { 
     self.init() 
     defer { 
      self.image = image 
      self.path = path 
     } 
    } 
} 

class CAPatternLayerDelegate: NSObject, CALayerDelegate { 
    static let sharedInstance = CAPatternLayerDelegate() 
    func draw(_ layer: CALayer, in ctx: CGContext) { 
     // cast layer to a CAPatternLayer so you can access properties 
     if let layer = layer as? CAPatternLayer, let image = layer.image, let path = layer.path { 
      // create a UIImageView to display the image, then render it to the context 
      let imageView = UIImageView() 
      // if a path bounding box was set, use it, otherwise draw over the whole layer 
      imageView.bounds = path.boundingBoxOfPath 
      imageView.image = image 
      imageView.layer.render(in: ctx) 
     } 
    } 
} 

А вот пример использования:

// create a path to fill 
let myMaskPath = UIBezierPath(rect: CGRect(x: 50, y: 25, width: 200, height: 100)) 
// pull the image and set it up as a resizeableImage with cap insets 
let patternImage = UIImage(named: "ground")!.resizableImage(withCapInsets: .init(top: 16, left: 16, bottom: 0, right: 16), resizingMode: .tile) 
// create the CAPatternLayer and add it to the view 
let myPatternLayer = CAPatternLayer(path: myMaskPath.cgPath, image: patternImage) 
view.layer.addSublayer(myPatternLayer) 

Выход:

9-slice tiled pattern