主頁 > 移動端開發 > iOS 基于 AVFoundation 制作的用于剪輯視頻專案

iOS 基于 AVFoundation 制作的用于剪輯視頻專案

2021-12-08 09:22:58 移動端開發

寫在前面

最近做了一個剪輯視頻的小專案,踩了一些小坑,但還是有驚無險的實作了功能,
其實 Apple 官方也給了一個 UIVideoEditController 讓我們來做視頻的處理,但難以進行擴展或者自定義,所以咱們就用 Apple 給的一個框架 AVFoundation 來開發自定義的視頻處理,
而且發現網上并沒有相關的并且比較系統的資料,于是寫下了本文,希望能對也在做視頻處理方面的新手(比如我)能帶來幫助,

專案效果圖

專案的功能大概就是對視頻軌道的撤銷、分割、洗掉還有拖拽視頻塊來對視頻擴展或者回退的功能
請添加圖片描述

功能實作

一、選取視頻并播放

通過 UIImagePickerController 選取視頻并且跳轉到自定義的編輯控制器
這一部分沒什么好說的
示例:

 //選擇視頻
       @objc func selectVideo() {
           if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
               //初始化圖片控制器
               let imagePicker = UIImagePickerController()
               //設定代理
               imagePicker.delegate = self
               //指定圖片控制器型別
               imagePicker.sourceType = .photoLibrary
               //只顯示視頻型別的檔案
               imagePicker.mediaTypes = [kUTTypeMovie as String]
               //彈出控制器,顯示界面
               self.present(imagePicker, animated: true, completion: nil)
           }
           else {
               print("讀取相冊錯誤")
           }
       }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        //獲取視頻路徑(選擇后視頻會自動復制到app臨時檔案夾下)
        guard let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else {
            return
        }
        let pathString = videoURL.relativePath
        print("視頻地址:\(pathString)")

        //圖片控制器退出
        self.dismiss(animated: true, completion: {
            let editorVC = EditorVideoViewController.init(with: videoURL)

            editorVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
            self.present(editorVC, animated: true) {

            }
        })
    }

二、按幀獲取縮略圖初始化視頻軌道

CMTime

在講實作方法之前先介紹一下 CMTime,CMTime 可以用于描述更精確的時間,比如我們想表達視頻中的一個瞬間例如 1:01 大多數時候你可以用 NSTimeInterval t = 61.0 這是沒有什么大問題的,但浮點數有個比較嚴重的問題就是無法精確的表達10的-6次方比如將一百萬個0.0000001相加,運算結果可能會變成1.0000000000079181,在視頻流傳輸的程序中伴隨著大量的資料加減,這樣就會造成誤差,所以我們需要另一種表達時間的方式,那就是 CMTime

CMTime是一種C函式結構體,有4個成員,
typedef struct {
CMTimeValue value; // 當前的CMTimeValue 的值
CMTimeScale timescale; // 當前的CMTimeValue 的參考標準 (比如:1000)
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
比如說平時我們所說的如果 timescale = 1000,那么 CMTimeValue = 1000 * 1 = 100

CMTimeScale timescale: 當前的CMTimeValue 的參考標準,它表示1秒的時間被分成了多少份,因為整個CMTime的精度是由它控制的所以它顯的尤為重要,例如,當timescale為1的時候,CMTime不能表示1秒一下的時間和1秒內的增長,相同的,當timescale為1000的時候,每秒鐘便被分成了1000份,CMTime的value便代表了多少毫秒,

實作方法

呼叫方法 generateCGImagesAsynchronously(forTimes requestedTimes: [NSValue], completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler)

 /**
    	@method			generateCGImagesAsynchronouslyForTimes:completionHandler:
    	@abstract		Returns a series of CGImageRefs for an asset at or near the specified times.
    	@param			requestedTimes
    					An NSArray of NSValues, each containing a CMTime, specifying the asset times at which an image is requested.
    	@param			handler
    					A block that will be called when an image request is complete.
    	@discussion		Employs an efficient "batch mode" for getting images in time order.
    					The client will receive exactly one handler callback for each requested time in requestedTimes.
    					Changes to generator properties (snap behavior, maximum size, etc...) will not affect outstanding asynchronous image generation requests.
    					The generated image is not retained.  Clients should retain the image if they wish it to persist after the completion handler returns.
    */
    open func generateCGImagesAsynchronously(forTimes requestedTimes: [NSValue], completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler)

瀏覽官方的注釋,可以看出需要傳入兩個引數 :
requestedTimes: [NSValue]:請求時間的陣列(型別為 NSValue)每一個元素包含一個 CMTime,用于指定請求視頻的時間,
completionHandler handler: @escaping AVAssetImageGeneratorCompletionHandler: 影像請求完成時將呼叫的塊,由于方法是異步呼叫的,所以需要回傳主執行緒更新 UI,

示例:

func splitVideoFileUrlFps(splitFileUrl:URL, fps:Float, splitCompleteClosure:@escaping (Bool, [UIImage]) -> Void) {
        var splitImages = [UIImage]()
		
		//初始化 Asset
        let optDict = NSDictionary(object: NSNumber(value: false), forKey: AVURLAssetPreferPreciseDurationAndTimingKey as NSCopying)
        let urlAsset = AVURLAsset(url: splitFileUrl, options: optDict as? [String : Any])

        let cmTime = urlAsset.duration
        let durationSeconds: Float64 = CMTimeGetSeconds(cmTime)

        var times = [NSValue]()
        let totalFrames: Float64 = durationSeconds * Float64(fps)
        var timeFrame: CMTime

		//定義 CMTime 即請求縮略圖的時間間隔
        for i in 0...Int(totalFrames) {
            timeFrame = CMTimeMake(value: Int64(i), timescale: Int32(fps))
            let timeValue = NSValue(time: timeFrame)

            times.append(timeValue)
        }

        let imageGenerator = AVAssetImageGenerator(asset: urlAsset)
        imageGenerator.requestedTimeToleranceBefore = CMTime.zero
        imageGenerator.requestedTimeToleranceAfter = CMTime.zero

        let timesCount = times.count
		
		//呼叫獲取縮略圖的方法
        imageGenerator.generateCGImagesAsynchronously(forTimes: times) { (requestedTime, image, actualTime, result, error) in

        var isSuccess = false
        switch (result) {
        case AVAssetImageGenerator.Result.cancelled:
            print("cancelled------")

        case AVAssetImageGenerator.Result.failed:
            print("failed++++++")

        case AVAssetImageGenerator.Result.succeeded:

            let framImg = UIImage(cgImage: image!)

            splitImages.append(self.flipImage(image: framImg, orientaion: 1))
            if (Int(requestedTime.value) == (timesCount-1)) { //最后一幀時 回呼賦值
                isSuccess = true
                splitCompleteClosure(isSuccess, splitImages)
                print("completed")
            }
        }
        }
    }

				//呼叫時利用回呼更新 UI
self.splitVideoFileUrlFps(splitFileUrl: url, fps: 1) { [weak self](isSuccess, splitImgs) in
            if isSuccess {
                //由于方法是異步的,所以需要回主執行緒更新 UI
                DispatchQueue.main.async {
                
                    }
                print("圖片總數目imgcount:\(String(describing: self?.imageArr.count))")
            }
        }

三、視頻指定時間跳轉

 /**
     @method			seekToTime:toleranceBefore:toleranceAfter:
     @abstract			Moves the playback cursor within a specified time bound.
     @param				time
     @param				toleranceBefore
     @param				toleranceAfter
     @discussion		Use this method to seek to a specified time for the current player item.
    					The time seeked to will be within the range [time-toleranceBefore, time+toleranceAfter] and may differ from the specified time for efficiency.
    					Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request sample accurate seeking which may incur additional decoding delay. 
    					Messaging this method with beforeTolerance:kCMTimePositiveInfinity and afterTolerance:kCMTimePositiveInfinity is the same as messaging seekToTime: directly.
     */
    open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime)

三個傳入的引數 time: CMTime, toleranceBefore: CMTime, tolearnceAfter: CMTime ,time 引數很好理解,即為想要跳轉的時間,那么后面兩個引數,按照官方的注釋理解,簡單來說為“誤差的容忍度”,他將會在你擬定的這個區間內跳轉,即為 [time-toleranceBefore, time+toleranceAfter] ,當然如果你傳 kCMTimeZero(在我當前的版本這個引數被被改為了 CMTime.zero),即為精確搜索,但是這會導致額外的解碼時間,

示例:

	let totalTime = self.avPlayer.currentItem?.duration
	let scale = self.avPlayer.currentItem?.duration.timescale
	
	//width:跳轉到的視頻軌長度 videoWidth:視頻軌總長度
 	let process = width / videoWidth
 	
      //快進函式
 	self.avPlayer.seek(to: CMTimeMake(value: Int64(totalTime * process * scale!), timescale: scale!), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero)

四、播放器監聽

通過播放器的監聽我們可以改變控制軌道的移動,達到視頻播放器和視頻軌道的聯動

/**
    	@method			addPeriodicTimeObserverForInterval:queue:usingBlock:
    	@abstract		Requests invocation of a block during playback to report changing time.
    	@param			interval
    	  The interval of invocation of the block during normal playback, according to progress of the current time of the player.
    	@param			queue
    	  The serial queue onto which block should be enqueued.  If you pass NULL, the main queue (obtained using dispatch_get_main_queue()) will be used.  Passing a
    	  concurrent queue to this method will result in undefined behavior.
    	@param			block
    	  The block to be invoked periodically.
    	@result
    	  An object conforming to the NSObject protocol.  You must retain this returned value as long as you want the time observer to be invoked by the player.
    	  Pass this object to -removeTimeObserver: to cancel time observation.
    	@discussion		The block is invoked periodically at the interval specified, interpreted according to the timeline of the current item.
    					The block is also invoked whenever time jumps and whenever playback starts or stops.
    					If the interval corresponds to a very short interval in real time, the player may invoke the block less frequently
    					than requested. Even so, the player will invoke the block sufficiently often for the client to update indications
    					of the current time appropriately in its end-user interface.
    					Each call to -addPeriodicTimeObserverForInterval:queue:usingBlock: should be paired with a corresponding call to -removeTimeObserver:.
    					Releasing the observer object without a call to -removeTimeObserver: will result in undefined behavior.
    */
    open func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

比較重要的一個引數是 interval: CMTime 這決定了代碼回呼的間隔時間,同時如果你在這個回呼里改變視頻軌道的 frame 那么這也會決定視頻軌道移動的流暢度

示例:

//player的監聽
        self.avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 120), queue: DispatchQueue.main) { [weak self](time) in
                //與軌道的聯動操作
        }

與快進方法沖突的問題

這個監聽方法和第三點中的快進方法會造成一個問題:當你拖動視頻軌道并且去快進的時候也會觸發這個回呼于是就造成了 拖動視頻軌道 frame (改變 frame) -> 快進方法 -> 觸發回呼 -> 改變 frame 這一個死回圈,那么就得添加判斷條件來不去觸發這個回呼,

快進方法與播放器聯動帶來的問題

播放視頻是異步的,并且快進方法解碼視頻需要時間,所以就導致了在雙方聯動的程序中帶來的時間差,并且當你認為視頻已經快進完成的時候,想要去改變視頻軌道的位置,由于解碼帶來的時間,導致了在回呼的時候會傳入幾個錯誤的時間,使得視頻軌道來回晃動,所以當前專案的做法是,回呼時需要判斷將要改變的 frame 是否合法(是否過大、過小)

ps:如果關于這兩個問題有更好的解決辦法,歡迎一起討論!

五、匯出視頻

 /**
        @method         insertTimeRange:ofTrack:atTime:error:
        @abstract       Inserts a timeRange of a source track into a track of a composition.
        @param          timeRange
                        Specifies the timeRange of the track to be inserted.
        @param          track
                        Specifies the source track to be inserted. Only AVAssetTracks of AVURLAssets and AVCompositions are supported (AVCompositions starting in MacOS X 10.10 and iOS 8.0).
        @param          startTime
                        Specifies the time at which the inserted track is to be presented by the composition track. You may pass kCMTimeInvalid for startTime to indicate that the timeRange should be appended to the end of the track.
        @param          error
                        Describes failures that may be reported to the user, e.g. the asset that was selected for insertion in the composition is restricted by copy-protection.
        @result         A BOOL value indicating the success of the insertion.
        @discussion
          You provide a reference to an AVAssetTrack and the timeRange within it that you want to insert. You specify the start time in the target composition track at which the timeRange should be inserted.
    
          Note that the inserted track timeRange will be presented at its natural duration and rate. It can be scaled to a different duration (and presented at a different rate) via -scaleTimeRange:toDuration:.
    */
    open func insertTimeRange(_ timeRange: CMTimeRange, of track: AVAssetTrack, at startTime: CMTime) throws

傳入的三個引數:
timeRange: CMTimeRange:指定要插入的視頻的時間范圍
track: AVAssetTrack:指定要插入的視頻軌道,僅支持AVURLAssets和AVCompositions的AvassetTrack(從MacOS X 10.10和iOS 8.0開始的AVCompositions),
starTime: CMTime: 指定合成視頻插入的時間點,可以傳遞kCMTimeInvalid 引數,以指定視頻應附加到前一個視頻的末尾,

示例:

	let composition = AVMutableComposition()
            //合并視頻、音頻軌道
    let videoTrack = composition.addMutableTrack(
                        withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID())
  	let audioTrack = composition.addMutableTrack(
                        withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())


 	let asset = AVAsset.init(url: self.url)

	var insertTime: CMTime = CMTime.zero

 	let timeScale = self.avPlayer.currentItem?.duration.timescale

			//回圈每個片段的資訊
	for clipsInfo in self.clipsInfoArr {
            
            //片段的總時間
		let clipsDuration = Double(Float(clipsInfo.width) / self.videoWidth) * self.totalTime
            
            //片段的開始時間
		let startDuration = -Float(clipsInfo.offset) / self.perSecondLength

      	do {
           	try videoTrack?.insertTimeRange(CMTimeRangeMake(start: CMTimeMake(value: Int64(startDuration * Float(timeScale!)), timescale: timeScale!), duration:CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!)), of: asset.tracks(withMediaType: AVMediaType.video)[0], at: insertTime)
                } catch _ {}

		do {
         	try audioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTimeMake(value: Int64(startDuration * Float(timeScale!)), timescale: timeScale!), duration:CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!)), of: asset.tracks(withMediaType: AVMediaType.audio)[0], at: insertTime)
      		 } catch _ {}
   		insertTime = CMTimeAdd(insertTime, CMTimeMake(value: Int64(clipsDuration * Double(timeScale!)), timescale: timeScale!))
            }

  		videoTrack?.preferredTransform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)

            //獲取合并后的視頻路徑
  		let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask,true)[0]

  		let destinationPath = documentsPath + "/mergeVideo-\(arc4random()%1000).mov"
     	print("合并后的視頻:\(destinationPath)")

end:通過這幾個 API 再加上互動的邏輯就能實作完整的剪輯功能啦!如果文中有不足的地方,歡迎指出!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/375943.html

標籤:其他

上一篇:Android實作高德地圖軌跡回放,android開發遇到的技術難點

下一篇:10個問題帶你看懂 Compose Multiplatform 1.0

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more