Swift 4中如何通过UIImagePicker正确选取并识别GIF图片?
Hey there! I’ve dealt with this exact problem before—UIImagePicker defaults to pulling just the first frame of a GIF as a static UIImage, which is totally annoying when you need full animated control. Let’s break down how to grab the actual animated data, then add play/stop functionality.
Step 1: Grab the Raw GIF Data from the Asset
The key issue is that info[.originalImage] only gives you a static frame. Instead, we need to fetch the full GIF data using the asset reference URL. Here’s how to do that in the picker delegate method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) // Fetch the PHAsset using the reference URL guard let assetURL = info[.referenceURL] as? URL else { print("Couldn't get asset URL") return } let fetchRequest = PHAsset.fetchAssets(withALAssetURLs: [assetURL], options: nil) guard let gifAsset = fetchRequest.firstObject else { print("Couldn't fetch GIF asset") return } // Request the raw image data (this gives us the full GIF file) let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = true requestOptions.deliveryMode = .highQualityFormat PHImageManager.default().requestImageDataAndOrientation(for: gifAsset, options: requestOptions) { data, _, _, _ in guard let gifData = data else { print("No GIF data found") return } // Now we have the full GIF data—pass it to our setup method self.setupGIFPlayer(with: gifData) } }
Step 2: Option 1 - Animate with UIImageView (Native & Simple)
If you don’t need super granular control, UIImageView has built-in animation support. We’ll parse the GIF frames and their timings, then set up the view:
func setupGIFPlayer(with gifData: Data) { guard let imageSource = CGImageSourceCreateWithData(gifData as CFData, nil) else { print("Couldn't create image source from GIF data") return } let frameCount = CGImageSourceGetCount(imageSource) var animationFrames: [UIImage] = [] var totalAnimationDuration: TimeInterval = 0.0 // Extract each frame and its delay time for frameIndex in 0..<frameCount { // Get the frame image guard let cgFrame = CGImageSourceCreateImageAtIndex(imageSource, frameIndex, nil) else { continue } animationFrames.append(UIImage(cgImage: cgFrame)) // Get the frame's delay time from GIF properties guard let frameProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, frameIndex, nil) as? [CFString: Any], let gifProperties = frameProperties[kCGImagePropertyGIFDictionary] as? [CFString: Any], let delayTime = gifProperties[kCGImagePropertyGIFDelayTime] as? TimeInterval else { // Fallback to 0.1s if we can't get the delay totalAnimationDuration += 0.1 continue } totalAnimationDuration += delayTime } // Create the animated image view let gifImageView = UIImageView(frame: CGRect(x: 40, y: 100, width: view.bounds.width - 80, height: 300)) gifImageView.contentMode = .scaleAspectFit gifImageView.animationImages = animationFrames gifImageView.animationDuration = totalAnimationDuration gifImageView.animationRepeatCount = 0 // Infinite loop view.addSubview(gifImageView) // Add play/stop toggle button let toggleButton = UIButton(type: .system) toggleButton.setTitle("Play", for: .normal) toggleButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold) toggleButton.frame = CGRect(x: 40, y: 420, width: view.bounds.width - 80, height: 50) toggleButton.addTarget(self, action: #selector(toggleGIFAnimation(sender:)), for: .touchUpInside) // Tag the button to link it to the image view toggleButton.tag = 100 view.addSubview(toggleButton) } @objc private func toggleGIFAnimation(sender: UIButton) { guard let gifImageView = view.viewWithTag(100) as? UIImageView else { return } if gifImageView.isAnimating { gifImageView.stopAnimating() sender.setTitle("Play", for: .normal) } else { gifImageView.startAnimating() sender.setTitle("Stop", for: .normal) } }
Step 2: Option 2 - Use AVPlayer for Better Control
If you want more advanced features (like seeking, precise loop control, or lower memory usage for large GIFs), treat the GIF like a video and use AVPlayer:
// Declare this as a class property to keep a reference to the player var gifPlayer: AVPlayer? func setupGIFPlayer(with gifData: Data) { // Save the GIF to a temporary file (AVPlayer needs a URL) let tempDir = FileManager.default.temporaryDirectory let tempGifURL = tempDir.appendingPathComponent("temp_animated.gif") do { try gifData.write(to: tempGifURL) } catch { print("Failed to write temp GIF file: \(error.localizedDescription)") return } // Set up AVPlayer and PlayerLayer let player = AVPlayer(url: tempGifURL) let playerLayer = AVPlayerLayer(player: player) playerLayer.frame = CGRect(x: 40, y: 100, width: view.bounds.width - 80, height: 300) playerLayer.videoGravity = .resizeAspect view.layer.addSublayer(playerLayer) // Add play/stop button let toggleButton = UIButton(type: .system) toggleButton.setTitle("Play", for: .normal) toggleButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold) toggleButton.frame = CGRect(x: 40, y: 420, width: view.bounds.width - 80, height: 50) toggleButton.addTarget(self, action: #selector(toggleAVPlayer(sender:)), for: .touchUpInside) view.addSubview(toggleButton) // Store the player reference self.gifPlayer = player } @objc private func toggleAVPlayer(sender: UIButton) { guard let player = self.gifPlayer else { return } if player.timeControlStatus == .playing { player.pause() sender.setTitle("Play", for: .normal) } else { player.play() sender.setTitle("Stop", for: .normal) } }
Important Notes
- Permissions: Don’t forget to add
NSPhotoLibraryUsageDescriptionto yourInfo.plistto request album access from the user. - Performance: For large GIFs, parsing all frames into
UIImagecan use a lot of memory. TheAVPlayerapproach is more efficient here. - Error Handling: I added basic error checks, but you’ll want to expand these for production code (e.g., handling cases where the asset isn’t a GIF).
内容的提问来源于stack exchange,提问作者PvDev

