2017-08-27 112 views
0

添加NSLayoutContraints在什麼我做錯了虧損,這是我的自定義的UIButton:不能自定義的UIButton

var btnNext: IteratorChevronButton { 
    let btn = IteratorChevronButton() 
    btn.translatesAutoresizingMaskIntoConstraints = false 
    return btn 
} 

func doInit() { 
    self.addSubview(btnNext) 

    self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal, 
              toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 
    self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal, 
              toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 
    self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0)) 
    self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 10)) 
} 

我:IteratorChevronButton在一個UIView類

import UIKit 

class IteratorChevronButton: UIButton { 

    required init() { 
     super.init(frame: .zero) 

     self.setBackgroundImage(UIImage(named: "icon-chevron-right"), for: .normal) 
    } 

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

} 

用法得到以下錯誤: enter image description here

我試圖讓btnNext懶,但我得到以下錯誤:

enter image description here

這裏是我的自定義UIView類代碼:

import UIKit 
import AVFoundation 
import RealmSwift 

enum PlayerError { 
    case unknownError 
} 
class Player: UIView { 
    let circularSliderVerticalPostionString:String = "75" 
    let circularSliderWidthString:String = "180" 
    let circularSliderHeightString = "180" 
    var circularSliderWidth:CGFloat! 
    var circularSliderHeight:CGFloat! 
    let uiImageIconClose = UIImage(named: "icon-close") 
    var movieDimension: CGSize = CGSize.zero 
    var imageGenerator: AVAssetImageGenerator! 
    var duration: CMTime = CMTimeMake(0, 30) 
    var avPlayerLayer: AVPlayerLayer? 
    var avPlayer: AVPlayer! 
    var startedDragging: Bool = false 
    var ready: Bool = false 
    var gForce: Double = 0.0 
    var isInDoublePlayer:Bool = false //used as a User Runtime Define Attribute in DoublePlayerViewController.xib 

    lazy var canvas: DrawingLayerView = { 
     let dv = DrawingLayerView() 
     return dv 
    }() 

    //Set this variable to swithch between normal playback and slow mo 
    var playSlowMo: Bool { 
     get { 
      return playerToolBar.playUsingTimer 
     } 
     set { 
      playerToolBar.playUsingTimer = newValue 
     } 
    } 

    //This when set the playback will resume after user stop dragging... I think its worth showing to 
    //some of the customers, if I were a player I would like it to be like this :) 
    var continuePlaybackWhenUserStopDragging: Bool { 
     get { 
      return playerToolBar.autoPlayWhenStopDragging 
     } 
     set { 
      playerToolBar.autoPlayWhenStopDragging = newValue 
     } 
    } 

    var playbackComlete: ((_ error: PlayerError?) -> Void)? = nil 

    lazy var controlBarSize: CGSize = { 
     return CGSize(width: self.bounds.width*3/4, height: 100) 
    }() 

    lazy var playerToolBar: PlayerToolBar = {[unowned self] in 
     let bar = PlayerToolBar(frame: CGRect.zero) 
     bar.translatesAutoresizingMaskIntoConstraints = false 
     self.addSubview(bar) 
     return bar 
    }() 

    let controlsBar: UIView = { 
     let view = UIView() 
     view.translatesAutoresizingMaskIntoConstraints = false 
     return view 
    }() 

    lazy var closeButton: UIButton = { 
     let btn = ExtendedBoundsButton(type: .custom) 
     btn.translatesAutoresizingMaskIntoConstraints = false 
     btn.setImage(self.uiImageIconClose, for: UIControlState()) 
     btn.setTitleColor(UIColor.blue, for: UIControlState()) 
     btn.isHidden = true 
     self.addSubview(btn) 
     return btn 
    }() 

    lazy var progressLabel: UILabel = { 
     let label = UILabel() 
     label.textColor = UIColor.white 
     return label 
    }() 

    var btnNext: IteratorChevronButton { 
     let btn = IteratorChevronButton() 
     btn.translatesAutoresizingMaskIntoConstraints = false 
     return btn 
    } 

    lazy var chevronImageRight: UIImageView = { 
     let image = UIImage(named:"icon-chevron-right")! 
     let imageView = UIImageView(image: image) 
     imageView.contentMode = .scaleToFill 
     imageView.translatesAutoresizingMaskIntoConstraints = false 

     return imageView 
    }() 

    lazy var circularSlider: BWCircularSliderView = { 
     let cs = BWCircularSliderView() 
     cs.translatesAutoresizingMaskIntoConstraints = false 
     cs.frame.size.width = self.circularSliderWidth 
     cs.frame.size.height = self.circularSliderHeight 
     return cs 
    }() 

    var exporter: AVAssetExportSession? = nil 
    var autoPlay: Bool = false 
    var progressTimer: Timer? 
    var movieDidPlay: (()->Void?)? = nil 



    var onTap: (()-> Void)? 

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

     doInit() 
    } 

    func doInit() { 
     self.circularSliderWidth = CGFloat(Int(circularSliderWidthString)!) 
     self.circularSliderHeight = CGFloat(Int(circularSliderHeightString)!) 
     self.addSubview(chevronImageRight) 
     self.addSubview(progressLabel) 
     self.addSubview(circularSlider) 
     self.addSubview(btnNext) 

     self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal, 
               toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 
     self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal, 
               toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 
     self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0) 
) 
     self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 10)) 

     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-\(circularSliderVerticalPostionString)-[circularSlider(\(circularSliderWidthString))]", options: [], metrics: nil, views: ["circularSlider": circularSlider])) 
     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[circularSlider(\(circularSliderHeightString))]|", options: [], metrics: nil, views: ["circularSlider": circularSlider])) 
     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[toolbar(100)]-0-|", options: [], metrics: nil, views: ["toolbar": playerToolBar])) 
     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[toolbar]|", options: [], metrics: nil, views: ["toolbar": playerToolBar])) 

     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-35-[btn(40)]", options: [], metrics: nil, views: ["btn": closeButton])) 
     self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[btn(40)]-10-|", options: [], metrics: nil, views: ["btn": closeButton])) 

     closeButton.addTarget(self, action: #selector(onClose), for: .touchUpInside) 

    } 

    func onClose() { 
     if !ready { 
      return 
     } 

     if let periodicTimeObserver = playerToolBar.periodicTimeObserver { 
      self.avPlayer.removeTimeObserver(periodicTimeObserver) 
     } 
     self.avPlayer.pause() 
     progressTimer?.invalidate() 
     playbackComlete?(nil) 

    } 

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

    override func layoutSubviews() { 
     super.layoutSubviews() 

     playerToolBar.isInDoublePlayer = self.isInDoublePlayer 
     self.circularSlider.isHidden = self.isInDoublePlayer 

     self.circularSlider.gForce = self.gForce 

     if let avPlayerLayer = avPlayerLayer { 
      avPlayerLayer.bounds = self.bounds 
      avPlayerLayer.position = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2) 

      playerToolBar.avPlayer = avPlayer 
      playerToolBar.setupMovieScrollBar() 
      if autoPlay { 
       autoPlay = false 
       play() 
      } 
      movieDidPlay?() 
     } 
     progressLabel.frame = CGRect(x: frame.size.width/2-100, y: frame.size.height/2-15, width: 200, height: 30) 
     addSubview(canvas) 
     addSubview(playerToolBar) 
     addSubview(closeButton) 
     canvas.frame = bounds 
    } 

    func onExportTimer(_ sender: AnyObject) { 
     guard let exporter = exporter else { 
      return 
     } 
     progressLabel.text = "Processing " + String(Int(exporter.progress*100)) + "%" 
    } 

    func mergeFiles(_ items: [String], assetWithOnset: String?, mergeComplete: @escaping (_ fileName: String?)->Void) -> Void { 
     if (assetWithOnset == nil) { 
      mergeComplete(items.first!) 
      return 
     } 
     let composition = AVMutableComposition() 
     let track:AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID()) 
     var insertTime = kCMTimeZero 

     for item in items { 
      let sourceAsset = AVAsset(url: URL(fileURLWithPath: FileUtility.getPathForFileMovieDirectory(item))) 
      let tracks = sourceAsset.tracks(withMediaType: AVMediaTypeVideo) 
      print("\(item) \(sourceAsset.isPlayable)") // print true 
      print(sourceAsset.isExportable) // print true 
      print(sourceAsset.isReadable) // print true 
      if tracks.count > 0 { 
       let assetTrack:AVAssetTrack = tracks[0] as AVAssetTrack 
       do { 
        try track.insertTimeRange(CMTimeRangeMake(kCMTimeZero,sourceAsset.duration), of: assetTrack, at: insertTime) 
        insertTime = CMTimeAdd(insertTime, sourceAsset.duration) 
       } catch { 
        mergeComplete(nil) 
        return 
       } 
      } 
     } 

     let fusedFileName = "fused_" + assetWithOnset! 
     let fusedFilePath = FileUtility.getPathForFileMovieDirectory(fusedFileName) 
     let fusedFileUrl = URL(fileURLWithPath: fusedFilePath) 

     do { 
      //in case the file merging fails, the residual file will cause 
      //the file export fail everytime as the file exist 
      try FileManager.default.removeItem(atPath: fusedFilePath) 
     } catch { 

     } 

     exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPreset1280x720) 
     guard let exporter = exporter else { 
      return 
     } 
     exporter.outputURL = fusedFileUrl 
     exporter.outputFileType = AVFileTypeQuickTimeMovie 
     progressTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(onExportTimer(_:)), userInfo: nil, repeats: true); 

     exporter.exportAsynchronously(completionHandler: { 
      switch exporter.status{ 
      case AVAssetExportSessionStatus.failed: 
       if exporter.error != nil { 
        print("AVAssetExportSession failed \(exporter.error!)") 
       }else{ 
        print("AVAssetExportSession failed for unknown reason") 
       } 
       mergeComplete(nil) 
      case AVAssetExportSessionStatus.cancelled: 
       if exporter.error != nil { 
        print("AVAssetExportSession canceled \(exporter.error!)") 
       }else{ 
        print("AVAssetExportSession canceled for unknown reason") 
       } 
       mergeComplete(nil) 
      default: 

       do { 
        let realm = try Realm() 
        let movieClip = realm.object(ofType: MovieModel.self, forPrimaryKey: assetWithOnset) 
        try realm.write { 
         movieClip?.fusedFile = fusedFileName 
        } 
        //The files are released based on the usage count 
        MovieRepository.sharedInstance.release(file: movieClip?.fileName) 
        MovieRepository.sharedInstance.release(file: movieClip?.nextFile) 
        MovieRepository.sharedInstance.release(file: movieClip?.prevFile) 
       } catch { 

       } 
       mergeComplete(fusedFileName) 
       self.progressLabel.text = "" 
       self.progressLabel.isHidden = true 
       NotificationUtility.notifyReloadGallery() 
      } 
     }) 

    } 

    func setMovies(_ items: [String], itemWithOnset asset: String?, playbackCompletion completion: @escaping ((_ error: PlayerError?) -> Void)){ 
     playbackComlete = completion 
     closeButton.isHidden = false 
     mergeFiles(items, assetWithOnset: asset) { [weak self] (fileName) in 
      DispatchQueue.main.async(execute: {() -> Void in 

       if let fileName = fileName, let strongSelf = self { 
        let asset = AVAsset(url: URL(fileURLWithPath: FileUtility.getPathForFileMovieDirectory(fileName))) 
        let avplayerItem = AVPlayerItem(asset: asset) 
        strongSelf.progressTimer?.invalidate() 
        strongSelf.progressLabel.removeFromSuperview() 
        strongSelf.duration = asset.duration 
        strongSelf.avPlayer = AVPlayer(playerItem: avplayerItem) 
        if let playerLayer = strongSelf.avPlayerLayer { 
         playerLayer.removeFromSuperlayer() 
        } 
        strongSelf.avPlayerLayer = AVPlayerLayer(player: strongSelf.avPlayer) 
        strongSelf.avPlayerLayer?.zPosition = -1 //send to back 
        strongSelf.self.layer.addSublayer(strongSelf.avPlayerLayer!) 


        NotificationCenter.default.addObserver(strongSelf, selector: #selector(Player.currentFileDidFinish(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: avplayerItem) 
        print("Duration \(Float(CMTimeGetSeconds(strongSelf.duration)))") 
        print("Size \(strongSelf.movieDimension)") 
        strongSelf.ready = true 
        strongSelf.autoPlay = true; 
        strongSelf.setNeedsLayout() 

       } 
      }) 

     } 

    } 

    func setMovie(movieAsset: MovieModel, completion: @escaping()->Void) { 

     movieDidPlay = completion 
     autoPlay = true 
     playerToolBar.playeBackTimer?.invalidate() 
     playerToolBar.playeBackTimer = nil 
     var clipNames: [String] 
     var assetWithOnset: String? = nil 
     if let fusedFile = movieAsset.fusedFile { 
      clipNames = [fusedFile] 
     } else { 
      assetWithOnset = movieAsset.fileName 
      if let nextFile = movieAsset.nextFile { 
       clipNames = [movieAsset.prevFile!, movieAsset.fileName!, nextFile] 
      } else { 
       clipNames = [movieAsset.prevFile!, movieAsset.fileName!] 
      } 
     } 
     self.setMovies(clipNames, itemWithOnset: assetWithOnset, playbackCompletion: { (err) in 

     }) 
     closeButton.isHidden = true 
    } 
    func resolutionSizeForVideo(_ asset:AVAsset) -> CGSize? { 
     guard let track = asset.tracks(withMediaType: AVMediaTypeVideo).first else { return nil } 
     let size = track.naturalSize.applying(track.preferredTransform) 
     return CGSize(width: fabs(size.width), height: fabs(size.height)) 
    } 
    //MARK: The playback methods 
    func pause(){ 
     if ready { 
      playerToolBar.pause() 
     } 
    } 
    func play() { 
     if ready { 
      playerToolBar.play() 
     } 
    } 

    func currentFileDidFinish(_ notification: Notification) { 
    /* if let periodicTimeObserver = playerToolBar.periodicTimeObserver { 
      self.avPlayer.removeTimeObserver(periodicTimeObserver) 
     } 
     progressTimer?.invalidate() 
     playbackComlete?(error: nil)*/ 

     avPlayer.seek(to: CMTimeMake(0, 30)) 
     avPlayer.rate = 1.0 
    } 

    func stop() { 
     avPlayer?.pause() 
     avPlayer = nil 

     avPlayerLayer?.removeFromSuperlayer() 
     avPlayerLayer = nil 
    } 

    deinit { 
     NotificationCenter.default.removeObserver(self) 
    } 
    //MARK: 
} 

class CollectionViewThumbNailCell: UICollectionViewCell { 

    lazy var barView: UIView = { 
     let lbl = UIView() 
     lbl.contentMode = .scaleToFill 
     lbl.translatesAutoresizingMaskIntoConstraints = false 
     lbl.backgroundColor = UIColor.white 
     lbl.layer.cornerRadius = 2 
     lbl.clipsToBounds = true 
     return lbl 
    }() 
    override init(frame: CGRect) { 
     super.init(frame: frame) 
     contentView.addSubview(barView) 
    } 

    func configureMark(_ big: Bool) { 
     if big { 
      barView.frame = CGRect(x: bounds.size.width/2 - 2, y: 2, width: 4, height: bounds.size.height) 
     } else { 
      barView.frame = CGRect(x: bounds.size.width/2 - 2, y: bounds.size.height/2+2, width: 4, height: bounds.size.height/2) 
     } 
    } 
    required init?(coder aDecoder: NSCoder) { 
     fatalError("init(coder:) has not been implemented") 
    } 
} 
+0

是否有任何日誌或只是崩潰? –

+0

@AhmadF只是一個崩潰,沒有日誌... –

+0

我認爲你應該只使用約束或只設置框架,在你的超類你使用的框架和子類中使用的約束,此外,你只設置尾隨和Y約束,所以它的寬度和高度沒有被正確計算 – 3stud1ant3

回答

4

我猜想錯誤是由於make btnNext

var btnNext: IteratorChevronButton = { 
    let btn = IteratorChevronButton() 
    btn.translatesAutoresizingMaskIntoConstraints = false 
    return btn 
}() 
3

您實例每次訪問btnNext時間一個新的按鈕。因此,在將一個實例添加爲子視圖之後,您可以使用其他實例來創建約束。由於這些其他子視圖不是self的子視圖,因此應用程序崩潰。

使它成爲一個懶惰的VAR初始化它只有一次:

lazy var btnNext: IteratorChevronButton = { 
    let btn = IteratorChevronButton() 
    btn.translatesAutoresizingMaskIntoConstraints = false 
    return btn 
}() 

您還可以在widthheight約束添加到按鈕本身,而不是self

btnNext.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 
btnNext.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128)) 

更新:

As Ahmad F陳述了在這種情況下,關鍵字是完全可選的。你可以簡單地實例化的一個按鈕,如果你一定會使用它:

var btnNext: IteratorChevronButton { 
    let btn = IteratorChevronButton() 
    btn.translatesAutoresizingMaskIntoConstraints = false 
    return btn 
} 

使用這種語法:這個(沒有被正確地使你btnNext)

var btnNext: IteratorChevronButton = { 
    let btn = IteratorChevronButton() 
    btn.translatesAutoresizingMaskIntoConstraints = false 
    return btn 
}() 
+0

謝謝,我更新了我的問題,由於某種原因,我無法讓自定義按鈕變得懶惰,當自定義按鈕很懶時,請檢查我的問題以獲取錯誤 –

+0

@BrianOgden不要忘記** = **和尾隨** ()** –

+1

我想如果他非常確定他會使用'btnNext',那麼沒有理由聲明它是一個懶惰的。 –