2017-12-09 3 views
3

Swift 4.0으로 팝업 된 SwiftColorPicker (Matthias Schlemm)의 문제를 해결하려고합니다. 전체 PickerImage 클래스를 포함 할 것이므로 전체 컨텍스트를 볼 수 있습니다.Swift 4에서 null을 반환하는 CGDataProvider

let provider = CGDataProvider(data: mutableData) 

createImageFromData에서

import UIKit 
import ImageIO 

open class PickerImage { 
    var provider:CGDataProvider! 
    var imageSource:CGImageSource? 
    var image:UIImage? 
    var mutableData:CFMutableData 
    var width:Int 
    var height:Int 

    fileprivate func createImageFromData(_ width:Int, height:Int) { 
     let colorSpace = CGColorSpaceCreateDeviceRGB() 
     let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) 
     let provider = CGDataProvider(data: mutableData) 
     imageSource = CGImageSourceCreateWithDataProvider(provider!, nil) 
     let cgimg = CGImage(width: Int(width), height: Int(height), bitsPerComponent: Int(8), bitsPerPixel: Int(32), bytesPerRow: Int(width) * Int(4), 
          space: colorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) 
     image = UIImage(cgImage: cgimg!) 
    } 

    func changeSize(_ width:Int, height:Int) { 
     self.width = width 
     self.height = height 
     let size:Int = width * height * 4 
     CFDataSetLength(mutableData, size) 
     createImageFromData(width, height: height) 
    } 

    init(width:Int, height:Int) { 
     self.width = width 
     self.height = height 
     let size:Int = width * height * 4 
     mutableData = CFDataCreateMutable(kCFAllocatorDefault, size) 
     createImageFromData(width, height: height) 
    } 

    open func writeColorData(_ h:CGFloat, a:CGFloat) { 

     let d = CFDataGetMutableBytePtr(self.mutableData) 

     if width == 0 || height == 0 { 
      return 
     } 

     var i:Int = 0 
     let h360:CGFloat = ((h == 1 ? 0 : h) * 360)/60.0 
     let sector:Int = Int(floor(h360)) 
     let f:CGFloat = h360 - CGFloat(sector) 
     let f1:CGFloat = 1.0 - f 
     var p:CGFloat = 0.0 
     var q:CGFloat = 0.0 
     var t:CGFloat = 0.0 
     let sd:CGFloat = 1.0/CGFloat(width) 
     let vd:CGFloat = 1/CGFloat(height) 

     var double_s:CGFloat = 0 
     var pf:CGFloat = 0 
     let v_range = 0..<height 
     let s_range = 0..<width 

     for v in v_range { 
      pf = 255 * CGFloat(v) * vd 
      for s in s_range { 
       i = (v * width + s) * 4 
       d?[i] = UInt8(255) 
       if s == 0 { 
        q = pf 
        d?[i+1] = UInt8(q) 
        d?[i+2] = UInt8(q) 
        d?[i+3] = UInt8(q) 
        continue 
       } 

       double_s = CGFloat(s) * sd 
       p = pf * (1.0 - double_s) 
       q = pf * (1.0 - double_s * f) 
       t = pf * (1.0 - double_s * f1) 
       switch(sector) { 
       case 0: 
        d?[i+1] = UInt8(pf) 
        d?[i+2] = UInt8(t) 
        d?[i+3] = UInt8(p) 
       case 1: 
        d?[i+1] = UInt8(q) 
        d?[i+2] = UInt8(pf) 
        d?[i+3] = UInt8(p) 
       case 2: 
        d?[i+1] = UInt8(p) 
        d?[i+2] = UInt8(pf) 
        d?[i+3] = UInt8(t) 
       case 3: 
        d?[i+1] = UInt8(p) 
        d?[i+2] = UInt8(q) 
        d?[i+3] = UInt8(pf) 
       case 4: 
        d?[i+1] = UInt8(t) 
        d?[i+2] = UInt8(p) 
        d?[i+3] = UInt8(pf) 
       default: 
        d?[i+1] = UInt8(pf) 
        d?[i+2] = UInt8(p) 
        d?[i+3] = UInt8(q) 
       } 


      } 
     } 
    } 


} 

라인은 물론, 다음 라인 충돌을 야기하는 무 값을 반환한다. 이 스위프트에 잘 작동하고 여기에 3

은 디버거의 값은 다음과 같습니다

메모리 할당을 다루는 enter image description here

내 현재 스킬 셋 이상 조금이다, 그래서 나는 실제로 무슨 일이 일어나고 있는지와 사투를 벌인거야 이리. Swift 4.0에서 이와 관련된 변경 사항이있어 CGDataProvider 호출이 무효 값을 반환하게합니까?

편집 : 다음은 PickerImage 개체를 초기화하는 ColorPicker 클래스입니다.

import UIKit 
import ImageIO 

open class ColorPicker: UIView { 

    fileprivate var pickerImage1:PickerImage? 
    fileprivate var pickerImage2:PickerImage? 
    fileprivate var image:UIImage? 
    fileprivate var data1Shown = false 
    fileprivate lazy var opQueue:OperationQueue = {return OperationQueue()}() 
    fileprivate var lock:NSLock = NSLock() 
    fileprivate var rerender = false 
    open var onColorChange:((_ color:UIColor, _ finished:Bool)->Void)? = nil 


    open var a:CGFloat = 1 { 
     didSet { 
      if a < 0 || a > 1 { 
       a = max(0, min(1, a)) 
      } 
     } 
    } 

    open var h:CGFloat = 0 { // // [0,1] 
     didSet { 
      if h > 1 || h < 0 { 
       h = max(0, min(1, h)) 
      } 
      renderBitmap() 
      setNeedsDisplay() 
     } 

    } 
    fileprivate var currentPoint:CGPoint = CGPoint.zero 


    open func saturationFromCurrentPoint() -> CGFloat { 
     return (1/bounds.width) * currentPoint.x 
    } 

    open func brigthnessFromCurrentPoint() -> CGFloat { 
     return (1/bounds.height) * currentPoint.y 
    } 

    open var color:UIColor { 
     set(value) { 
      var hue:CGFloat = 1 
      var saturation:CGFloat = 1 
      var brightness:CGFloat = 1 
      var alpha:CGFloat = 1 
      value.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) 
      a = alpha 
      if hue != h || pickerImage1 === nil { 
       self.h = hue 
      } 
      currentPoint = CGPoint(x: saturation * bounds.width, y: brightness * bounds.height) 
      self.setNeedsDisplay() 
     } 
     get { 
      return UIColor(hue: h, saturation: saturationFromCurrentPoint(), brightness: brigthnessFromCurrentPoint(), alpha: a) 
     } 
    } 

    public override init(frame: CGRect) { 
     super.init(frame: frame) 
     commonInit() 
    } 

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

    func commonInit() { 
     isUserInteractionEnabled = true 
     clipsToBounds = false 
     self.addObserver(self, forKeyPath: "bounds", options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.initial], context: nil) 
    } 

    deinit { 
     self.removeObserver(self, forKeyPath: "bounds") 
    } 

    open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
     if keyPath == "bounds" { 
      if let pImage1 = pickerImage1 { 
       pImage1.changeSize(Int(self.bounds.width), height: Int(self.bounds.height)) 
      } 
      if let pImage2 = pickerImage2 { 
       pImage2.changeSize(Int(self.bounds.width), height: Int(self.bounds.height)) 
      } 
      renderBitmap() 
      self.setNeedsDisplay() 
     } else { 
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 
     } 
    } 

    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { 
     let touch = touches.first! as UITouch 
     handleTouche(touch, ended: false) 
    } 

    open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { 
     let touch = touches.first! as UITouch 
     handleTouche(touch, ended: false) 
    } 

    open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { 
     let touch = touches.first! as UITouch 
     handleTouche(touch, ended: true) 
    } 

    fileprivate func handleColorChange(_ color:UIColor, changing:Bool) { 
     if color !== self.color { 
      if let handler = onColorChange { 
       handler(color, !changing) 
      } 
      setNeedsDisplay() 
     } 
    } 

    fileprivate func handleTouche(_ touch:UITouch, ended:Bool) { 
     // set current point 
     let point = touch.location(in: self) 
     if self.bounds.contains(point) { 
      currentPoint = point 
     } else { 
      let x:CGFloat = min(bounds.width, max(0, point.x)) 
      let y:CGFloat = min(bounds.width, max(0, point.y)) 
      currentPoint = CGPoint(x: x, y: y) 
     } 
     handleColorChange(pointToColor(point), changing: !ended) 
    } 

    fileprivate func pointToColor(_ point:CGPoint) ->UIColor { 
     let s:CGFloat = min(1, max(0, (1.0/bounds.width) * point.x)) 
     let b:CGFloat = min(1, max(0, (1.0/bounds.height) * point.y)) 
     return UIColor(hue: h, saturation: s, brightness: b, alpha:a) 
    } 

    fileprivate func renderBitmap() { 
     if self.bounds.isEmpty { 
      return 
     } 
     if !lock.try() { 
      rerender = true 
      return 
     } 
     rerender = false 

     if pickerImage1 == nil { 
      self.pickerImage1 = PickerImage(width: Int(bounds.width), height: Int(bounds.height)) 
      self.pickerImage2 = PickerImage(width: Int(bounds.width), height: Int(bounds.height)) 
     } 

     opQueue.addOperation {() -> Void in 
      // Write colors to data array 
      if self.data1Shown { self.pickerImage2!.writeColorData(self.h, a:self.a) } 
      else { self.pickerImage1!.writeColorData(self.h, a:self.a)} 


      // flip images 
//   self.image = self.data1Shown ? self.pickerImage2!.image! : self.pickerImage1!.image! 
      self.data1Shown = !self.data1Shown 

      // make changes visible 
      OperationQueue.main.addOperation({() -> Void in 
       self.setNeedsDisplay() 
       self.lock.unlock() 
       if self.rerender { 
        self.renderBitmap() 
       } 
      }) 

     } 

    } 



    open override func draw(_ rect: CGRect) { 
     if let img = image { 
      img.draw(in: rect) 
     } 

     //// Oval Drawing 
     let ovalPath = UIBezierPath(ovalIn: CGRect(x: currentPoint.x - 5, y: currentPoint.y - 5, width: 10, height: 10)) 
     UIColor.white.setStroke() 
     ovalPath.lineWidth = 1 
     ovalPath.stroke() 

     //// Oval 2 Drawing 
     let oval2Path = UIBezierPath(ovalIn: CGRect(x: currentPoint.x - 4, y: currentPoint.y - 4, width: 8, height: 8)) 
     UIColor.black.setStroke() 
     oval2Path.lineWidth = 1 
     oval2Path.stroke() 
    } 

} 

답변

2

그것은 스위프트 4의 문제가 아니지만, 아이폰 OS (11)의 문제가 당신은 당신의 코드 는 아이폰 OS 10 시뮬레이터에를 작동 찾을 수 있습니다.

원본 코드는 겉으로보기에는 iOS 10에서 작동하지만, 운에 따라 다릅니다. 코드의 이러한 부분에서

: sizeempty (that is, content-less) :

init(width:Int, height:Int) { 
    self.width = width 
    self.height = height 
    let size:Int = width * height * 4 
    mutableData = CFDataCreateMutable(kCFAllocatorDefault, size) 
    createImageFromData(width, height: height) 
} 

속성 mutableData는 용량 CFMutableData로 초기화된다.

그리고 iOS 11에서 이니셜 라이저 CGDataProvider.init(data:)은 데이터 제공자로 비워 두어서는 안되기 때문에 CFData 빈을 거부합니다.

빠른 수정은 다음과 같이 될 것이다 :

init(width:Int, height:Int) { 
    self.width = width 
    self.height = height 
    let size:Int = width * height * 4 
    mutableData = CFDataCreateMutable(kCFAllocatorDefault, size) 
    CFDataSetLength(mutableData, size) //<-set the length of the data 
    createImageFromData(width, height: height) 
} 

하지만 아이폰 OS (11)

+1

그게 다야! 데이터의 길이를 설정하면 iOS 11 (심지어 어쨌든) 용도로 배포 할 때도 수정됩니다. 많은 감사합니다. – Lastmboy

+0

당신은 영웅입니다! –

1

문제는 당신이 그 라인에 let을 추가하고 기능 createImageFromData 내에서 새로운 변수 호출 provider를 생성합니다. 이렇게하면 클래스의 provider 매개 변수로 전달 된 적이 없으며 항상 null입니다. let 만 제거하면 제대로 작동합니다.

fileprivate func createImageFromData(_ width:Int, height:Int) { 
    let colorSpace = CGColorSpaceCreateDeviceRGB() 
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) 
    provider = CGDataProvider(data: mutableData) 
    imageSource = CGImageSourceCreateWithDataProvider(provider!, nil) 
    let cgimg = CGImage(width: Int(width), height: Int(height), bitsPerComponent: Int(8), bitsPerPixel: Int(32), bytesPerRow: Int(width) * Int(4), 
         space: colorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) 
    image = UIImage(cgImage: cgimg!) 
} 
+0

감사에서 예상대로 코드의 다른 부분 일 것입니다 확실하지 않다. 나는 그것을 완전히 놓쳤다. 그러나 여전히'Thread 1 : 치명적 오류 :'imageSource = CGImageSourceCreateWithDataProvider (provider!, nil)'에 대해 선택적 값을 언 래핑하는 동안 예기치 않게 nil을 발견했습니다. 공급자는 여전히 null입니다. – Lastmboy

+0

그것은 나를 위해 일하고있다. ImagePicker 클래스가 생성 될 때 어떤 값을주고 있습니까? – barbarity

+0

PickerImage가 너비 : 240 높이 : 128로 초기화되었습니다. – Lastmboy