Muhammad Ahsan
Muhammad Ahsan

Reputation: 1

Sharing to facebook stories ios

i am trying share screenshot or screen record of my app, i have implemented instagram also that is works fine, but for facebook it open the open my screenshot or screen record in pasteboard then again open my app, follow in instructions from there official website, but could not fine the solution

import Foundation import UIKit import AVFoundation

class FacebookSharingManager { static let shared = FacebookSharingManager()

private let appID: String = "1337677113908275"

/// Shares media (video or screenshot) to Facebook Stories
func shareToFacebookStories(videoURL: URL? = nil, screenshot: UIImage? = nil) {
    
    if let videoURL = videoURL {
        shareBackgroundVideo(videoURL: videoURL, appID: appID)
    } else if let screenshot = screenshot {
        shareImage(screenshot: screenshot, appID: appID)
    } else {
        print("⚠️ No valid media provided.")
    }
}

/// Shares an image to Facebook Stories
private func shareImage(screenshot: UIImage, appID: String) {
    let resizedImage = resizeImageToAspectRatio(image: screenshot, targetRatio: 9.0/16.0)
    if let imageData = resizedImage.pngData() {
        backgroundImage(imageData: imageData, appID: appID)
    }
}

private func backgroundImage(imageData: Data, appID: String) {
    guard let urlScheme = URL(string: "facebook-stories://share?source_appliation=\(appID)"),
          UIApplication.shared.canOpenURL(urlScheme) else {
        print("❌ Facebook app is not installed or does not support story sharing.")
        return
    }
    
    let pasteboardItems: [[String: Any]] = [
        ["com.facebook.sharedSticker.backgroundImage": imageData,
         "com.facebook.sharedSticker.appID": appID]
    ]
    
    let pasteboardOptions: [UIPasteboard.OptionsKey: Any] = [
        .expirationDate: Date().addingTimeInterval(300) // 5 minutes expiration
    ]
    // Assign background image to pasteboard
    UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)
    
    // Open Facebook Stories
    UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
}

func shareBackgroundVideo(videoURL: URL, appID: String) {
    // ✅ Process video before sharing
    processVideoForFacebook(inputURL: videoURL) { processedURL in
        guard let finalURL = processedURL else {
            print("❌ Failed to process video.")
            return
        }
        
        Task {
            if await self.isValidVideo(videoURL: finalURL) {
                do {
                    let videoData = try Data(contentsOf: finalURL)
                    self.backgroundVideo(videoData: videoData, appID: appID)
                } catch {
                    print("❌ Failed to load video data: \(error.localizedDescription)")
                }
            } else {
                print("⚠️ Video still does not meet Facebook's requirements!")
            }
        }
    }
}

func backgroundVideo(videoData: Data, appID: String) {
    guard let urlScheme = URL(string: "facebook-stories://share?source_application\(appID)"),
          UIApplication.shared.canOpenURL(urlScheme) else {
        print("❌ Facebook app is not installed or does not support story sharing.")
        return
    }
    
    let pasteboardItems: [[String: Any]] = [
        ["com.facebook.sharedSticker.backgroundVideo": videoData,
         "com.facebook.sharedSticker.appID": appID]
    ]
    
    let pasteboardOptions: [UIPasteboard.OptionsKey: Any] = [
        .expirationDate: Date().addingTimeInterval(300) // 5 minutes expiration
    ]
    
    // Assign background video to pasteboard
    UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)
    
    DispatchQueue.main.async {
        UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
    }
}

private func showAlert(title: String, message: String) {
    DispatchQueue.main.async {
        if let topVC = self.getTopViewController() {
            let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            topVC.present(alert, animated: true)
        } else {
            print("❌ Failed to find top view controller.")
        }
    }
}

// Helper function to get the top-most view controller
private func getTopViewController() -> UIViewController? {
    if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
       let window = windowScene.windows.first,
       let rootViewController = window.rootViewController {
        var topController: UIViewController? = rootViewController
        while let presentedViewController = topController?.presentedViewController {
            topController = presentedViewController
        }
        return topController
    }
    return nil
}
func resizeImageToAspectRatio(image: UIImage, targetRatio: CGFloat) -> UIImage {
    let originalSize = image.size
    let originalRatio = originalSize.width / originalSize.height
    
    var newSize: CGSize
    if originalRatio > targetRatio {
        // Crop width to fit 9:16
        newSize = CGSize(width: originalSize.height * targetRatio, height: originalSize.height)
    } else  {
        // Crop height to fit 9:16
        newSize = CGSize(width: originalSize.width, height: originalSize.width / targetRatio)
    }
    
    let format = UIGraphicsImageRendererFormat()
    format.scale = image.scale
    let renderer = UIGraphicsImageRenderer(size: newSize, format: format)
    
    return renderer.image { _ in
        image.draw(in: CGRect(origin: .zero, size: newSize))
    }
}

/// ✅ **Validates if video meets Facebook's requirements**
private func isValidVideo(videoURL: URL) async -> Bool {
    let asset = AVURLAsset(url: videoURL)
    
    do {
        // Load required properties asynchronously
        let duration = try await asset.load(.duration)
        let tracks = try await asset.loadTracks(withMediaType: .video)
        
        guard let track = tracks.first else {
            print("❌ No video track found.")
            return false
        }
        
        let size = try await track.load(.naturalSize)
        let bitrate = try await track.load(.estimatedDataRate)
        
        let durationSeconds = CMTimeGetSeconds(duration)
        
        print("📹 Video Info: Duration = \(durationSeconds)s, Resolution = \(size.width)x\(size.height), Bitrate = \(bitrate)")
        
        // ✅ Ensure video meets Facebook’s recommended criteria
        return durationSeconds <= 20 &&
        size.width >= 1080 &&
        bitrate <= 50_000_000 // 50 Mbps max recommended
        
    } catch {
        print("❌ Error loading video properties: \(error.localizedDescription)")
        return false
    }
}

func processVideoForFacebook(inputURL: URL, completion: @escaping (URL?) -> Void) {
    Task {
        let asset = AVURLAsset(url: inputURL)
        
        // ✅ Ensure export session is created outside the Task
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
            print("❌ Failed to create AVAssetExportSession")
            completion(nil)
            return
        }
        
        do {
            // ✅ Load required properties asynchronously
            let duration = try await asset.load(.duration)
            let tracks = try await asset.loadTracks(withMediaType: .video)
            
            guard let videoTrack = tracks.first else {
                print("❌ No video track found.")
                completion(nil)
                return
            }
            
            let preferredTransform = try await videoTrack.load(.preferredTransform)
            
            // ✅ Define output file path
            let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("processedVideo.mp4")
            try? FileManager.default.removeItem(at: outputURL)
            
            // ✅ Force video to 1080x1920 resolution
            let videoComposition = AVMutableVideoComposition()
            videoComposition.renderSize = CGSize(width: 1080, height: 1920)
            videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // 30 FPS
            
            let instruction = AVMutableVideoCompositionInstruction()
            instruction.timeRange = CMTimeRange(start: .zero, duration: duration)
            
            let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
            transformer.setTransform(preferredTransform, at: .zero)
            
            instruction.layerInstructions = [transformer]
            videoComposition.instructions = [instruction]
            
            exportSession.outputURL = outputURL
            exportSession.outputFileType = .mp4
            exportSession.shouldOptimizeForNetworkUse = true
            exportSession.videoComposition = videoComposition
            
            // ✅ Start exporting asynchronously
            exportSession.exportAsynchronously {
                DispatchQueue.main.async { // ✅ Ensure completion runs on the main thread
                    if exportSession.status == .completed {
                        print("✅ Video processed successfully!")
                        completion(outputURL)
                    } else {
                        print("❌ Video processing failed: \ (exportSession.error?.localizedDescription ?? "Unknown error")")
                        completion(nil)
                    }
                }
            }
        } catch {
            print("❌ Failed to load video properties: \.     (error.localizedDescription)")
            completion(nil)
        }

this is my complete code i am expecting to open facebook and my provided content should be appear in pasteboard and i have general option to post that on stories

Upvotes: -1

Views: 21

Answers (0)

Related Questions