iOS 开发:活动视图、扩展与音频基础
1. UIActivity 与视图控制器交互
当
UIActivity
提供一个视图控制器作为其
activityViewController
时,需要提前将自身的引用传递给该视图控制器,以便视图控制器在合适的时候调用
activityDidFinish(_:)
方法。
例如,假设活动是让用户在某人的照片上画胡子。视图控制器会提供相应的界面,包括取消和完成按钮。当用户点击这些按钮时,会执行必要的操作(如用户点击完成时保存修改后的照片),然后调用
activityDidFinish(_:)
。
以下是实现
activityViewController
属性的代码:
override var activityViewController : UIViewController? {
let mvc = MustacheViewController(activity: self, items: self.items!)
return mvc
}
MustacheViewController
的代码如下:
weak var activity : UIActivity?
var items: [Any]
init(activity:UIActivity, items:[Any]) {
self.activity = activity
self.items = items
super.init(nibName: "MustacheViewController", bundle: nil)
}
// ... other stuff ...
@IBAction func doCancel(_ sender: Any) {
self.activity?.activityDidFinish(false)
}
@IBAction func doDone(_ sender: Any) {
self.activity?.activityDidFinish(true)
}
需要注意的是,
MustacheViewController
对
UIActivity
的引用(
self.activity
)是弱引用,否则会导致循环引用。
2. SFSafariViewController 代理方法
SFSafariViewController
代理方法
safariViewController(_:activityItemsFor:title:)
的目的是为活动视图添加自定义
UIActivity
项。因为该视图控制器的视图虽然显示在你的应用中,但它不是你的视图控制器,其分享按钮和活动视图也不是你的,所以需要实现此方法来添加自定义项。
3. 动作扩展(Action Extensions)
为了提供系统级的活动(即在其他应用弹出活动视图时显示),可以编写分享扩展(显示在上方行)或动作扩展(显示在下方行)。一个应用可以提供一个分享扩展,但可以提供多个动作扩展。
以下是编写动作扩展的基本步骤:
1.
选择目标模板
:从
iOS → Application Extension → Action Extension
开始。创建目标时,在第二个面板中选择是否带有界面。
2.
配置 Info.plist
:除了设置捆绑包名称(将显示在活动视图中活动图标下方),还需要指定该活动接受的数据类型。在
NSExtensionActivationRule
字典中提供一个或多个键,例如:
-
NSExtensionActivationSupportsFileWithMaxCount
-
NSExtensionActivationSupportsImageWithMaxCount
-
NSExtensionActivationSupportsMovieWithMaxCount
-
NSExtensionActivationSupportsText
-
NSExtensionActivationSupportsWebURLWithMaxCount
也可以通过编写
NSPredicate
字符串作为
NSExtensionActivationRule
键的值,以更复杂的方式声明活动接受的数据类型。
3.
指定图标
:动作扩展在活动视图中由一个图标表示,需要在动作扩展目标中指定该图标。图标大小与应用图标相同,可以使用应用图标。在动作扩展目标中添加资产目录,并在其中创建 iOS 应用图标,该图标将被视为模板图像。
以下是一个带有界面的动作扩展示例,该扩展接受一个可能是美国州缩写的字符串,并提供该州的实际名称:
let list : [String:String] = {
let path = Bundle.main.url(forResource:"abbrevs", withExtension:"txt")!
// ... load the text file as a string, parse into dictionary (result)
return result
}()
func state(for abbrev:String) -> String? {
return self.list[abbrev]
}
override func viewDidLoad() {
super.viewDidLoad()
self.doneButton.isEnabled = false
self.lab.text = "No expansion available."
// ...
}
if self.extensionContext == nil {
return
}
let items = self.extensionContext!.inputItems
let desiredType = kUTTypePlainText as String
guard let extensionItem = items[0] as? NSExtensionItem
else {return}
guard let provider = extensionItem.attachments?.first
else {return}
guard provider.hasItemConformingToTypeIdentifier(self.desiredType)
else {return}
provider.loadItem(forTypeIdentifier: desiredType) { item, err in
DispatchQueue.main.async {
if let orig = (item as? String)?.uppercased() {
self.orig = orig
if let exp = self.state(for:orig) {
self.expansion = exp
self.lab.text = """
Can expand \(orig) to \(exp).
Tap Done to place on clipboard.
"""
self.doneButton.isEnabled = true
}
}
}
}
@IBAction func cancel(_ sender: Any) {
self.extensionContext?.completeRequest(returningItems: nil)
}
@IBAction func done(_ sender: Any) {
UIPasteboard.general.string = self.expansion!
self.extensionContext?.completeRequest(returningItems: nil)
}
4. 扩展调试方法
扩展不在应用进程中运行,因此断点和日志记录无效。可以使用以下简单技术解决此问题:
1. 运行宿主应用,将其复制到目标设备。
2. 在 Xcode 窗口工具栏的方案弹出菜单中切换到扩展,然后运行扩展。
3. 弹出一个对话框,询问要运行的应用,选择宿主应用并点击运行。
4. 宿主应用将运行,然后调用扩展并进行测试。此时调试的是扩展,所有调试功能将按预期工作。
5. 分享扩展(Share Extensions)
如果应用提供分享扩展,它可以出现在活动视图的顶部行。分享扩展类似于动作扩展,但它不是处理接收到的数据,而是将数据以某种方式存储或发布到服务器。
分享扩展在活动视图的顶部行由应用自己的图标表示。用户点击该图标后,可以在完成分享操作之前进一步与数据交互,可能会修改或取消操作。创建分享扩展目标(
iOS → Application Extension → Share Extension
)时,模板会提供一个故事板和一个视图控制器,视图控制器可以是以下两种类型之一:
-
SLComposeServiceViewController
:提供一个标准界面,用于在
UITextView
中显示可编辑文本,可能带有预览,还有用户可配置的选项按钮,以及取消和发布按钮。
-
普通视图控制器子类
:如果选择普通视图控制器子类,则需要自己设计界面,包括提供关闭界面的方式。
无论选择哪种界面形式,关闭界面的方式都是调用:
self.extensionContext?.completeRequest(returningItems:nil)
以下是使用
SLComposeServiceViewController
的基本示例:
weak var config : SLComposeSheetConfigurationItem?
var selectedText = "Large" {
didSet {
self.config?.value = self.selectedText
}
}
override func configurationItems() -> [Any]! {
let c = SLComposeSheetConfigurationItem()!
c.title = "Size"
c.value = self.selectedText
c.tapHandler = { [unowned self] in
let tvc = TableViewController(style: .grouped)
tvc.selectedSize = self.selectedText
tvc.delegate = self
self.pushConfigurationViewController(tvc)
}
self.config = c
return [c]
}
protocol SizeDelegate : class {
var selectedText : String {get set}
}
class TableViewController: UITableViewController {
var selectedSize : String?
weak var delegate : SizeDelegate?
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at:indexPath)!
let s = cell.textLabel!.text!
self.selectedSize = s
self.delegate?.selectedText = s
tableView.reloadData()
}
// ...
}
override func didSelectPost() {
let s = self.contentText
// ... do something with it ...
self.extensionContext?.completeRequest(returningItems:nil)
}
可以通过以下代码将发布按钮改为保存按钮,但这种方法的合法性和在未来系统中的可用性不确定:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.navigationController?.navigationBar.topItem?
.rightBarButtonItem?.title = "Save"
}
6. 音频基础
iOS 提供了多种技术,允许应用产生、录制和处理声音。以下是一些基本信息:
-
播放界面选项
:本章讨论的类都没有在应用中提供传输界面(即允许用户停止和开始声音播放的界面)。如果需要传输界面,可以选择以下方法:
- 创建自己的界面。
- 将内置的“远程控制”按钮与应用关联。
- 网页视图支持 HTML5
<audio>
标签,这是一种简单、轻量级的播放音频和控制播放的方式(包括使用 AirPlay)。
- 将声音视为电影,并使用相关的界面提供类,这也是播放远程网络上声音文件的好方法。
7. 系统声音
系统声音是 iOS 中类似于基本计算机“哔声”的最简单声音形式,通过音频工具箱框架的系统声音服务实现,需要导入
AudioToolbox
。播放系统声音的 API 有两种形式:旧形式和新形式(iOS 9 引入),先介绍旧形式(仍然可用且未被弃用)。
旧形式涉及调用两个 C 函数之一:
-
AudioServicesPlayAlertSound
:在 iPhone 上,根据用户设置可能还会使设备振动。
-
AudioServicesPlaySystemSound
:在 iPhone 上不会伴随振动,但可以通过传递
kSystemSoundID_Vibrate
作为“声音”名称,将此“声音”指定为设备振动。
要播放的声音文件需要是未压缩的 AIFF 或 WAV 文件(或包装其中之一的 Apple CAF 文件)。需要通过调用
AudioServicesCreateSystemSoundID
并传入指向声音文件的 URL 来获取
SystemSoundID
。
以下是一个简单的示例:
let sndurl = Bundle.main.url(forResource:"test", withExtension: "aif")!
var snd : SystemSoundID = 0
AudioServicesCreateSystemSoundID(sndurl as CFURL, &snd)
AudioServicesPlaySystemSound(snd)
上述代码可以播放声音,但存在内存管理问题。需要调用
AudioServicesDisposeSystemSoundID
释放
SystemSoundID
,但
AudioServicesPlaySystemSound
是异步执行的,不能在调用播放函数的下一行调用释放函数,否则会在声音即将开始播放时释放声音,导致无声。
正确的方法是实现一个声音完成函数,在声音播放完成时调用。通过调用
AudioServicesAddSystemSoundCompletion
指定声音完成函数,该函数必须作为 C 函数指针提供,但 Swift 允许在需要 C 函数指针的地方传递全局或局部 Swift 函数(包括匿名函数)。以下是改进后的代码:
let sndurl = Bundle.main.url(forResource:"test", withExtension: "aif")!
var snd : SystemSoundID = 0
AudioServicesCreateSystemSoundID(sndurl as CFURL, &snd)
AudioServicesAddSystemSoundCompletion(snd, nil, nil, { snd, _ in
// 声音播放完成后的操作
})
综上所述,本文介绍了 iOS 开发中活动视图、扩展和音频基础的相关内容,包括 UIActivity 与视图控制器的交互、动作扩展和分享扩展的编写方法,以及系统声音的播放和内存管理。这些知识对于开发功能丰富的 iOS 应用非常重要。
iOS 开发:活动视图、扩展与音频基础
8. 音频播放与控制
在 iOS 开发中,除了系统声音,还有多种方式实现音频的播放与控制。以下为你详细介绍不同的实现途径及相关代码示例。
8.1 创建自定义播放界面
若要创建自定义的音频播放界面,可借助 UIKit 中的控件,如
UIButton
用于播放、暂停操作,
UISlider
用于调节音量等。以下是一个简单示例:
import UIKit
import AVFoundation
class AudioPlayerViewController: UIViewController {
var audioPlayer: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
// 加载音频文件
guard let audioURL = Bundle.main.url(forResource: "audio_file", withExtension: "mp3") else { return }
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
audioPlayer?.prepareToPlay()
} catch {
print("音频加载失败: \(error)")
}
// 创建播放按钮
let playButton = UIButton(frame: CGRect(x: 100, y: 200, width: 100, height: 50))
playButton.setTitle("播放", for: .normal)
playButton.addTarget(self, action: #selector(playAudio), for: .touchUpInside)
view.addSubview(playButton)
// 创建暂停按钮
let pauseButton = UIButton(frame: CGRect(x: 250, y: 200, width: 100, height: 50))
pauseButton.setTitle("暂停", for: .normal)
pauseButton.addTarget(self, action: #selector(pauseAudio), for: .touchUpInside)
view.addSubview(pauseButton)
}
@objc func playAudio() {
audioPlayer?.play()
}
@objc func pauseAudio() {
audioPlayer?.pause()
}
}
8.2 关联远程控制按钮
将内置的“远程控制”按钮与应用关联,可让用户通过设备的物理按钮控制音频播放。实现步骤如下:
1. 激活应用的音频会话:
import AVFoundation
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("音频会话设置失败: \(error)")
}
- 注册远程控制事件:
UIApplication.shared.beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
- 处理远程控制事件:
override func remoteControlReceived(with event: UIEvent?) {
guard let event = event, event.type == .remoteControl else { return }
switch event.subtype {
case .remoteControlPlay:
audioPlayer?.play()
case .remoteControlPause:
audioPlayer?.pause()
default:
break
}
}
8.3 使用 HTML5
<audio>
标签
在 Web 视图中使用 HTML5
<audio>
标签播放音频,可实现简单、轻量级的音频播放与控制。示例代码如下:
import UIKit
import WebKit
class WebAudioViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func loadView() {
let webConfig = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfig)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let html = """
<html>
<body>
<audio controls>
<source src="audio_file.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</body>
</html>
"""
webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)
}
}
9. 音频录制
iOS 还支持音频录制功能,可使用
AVAudioRecorder
类实现。以下是一个简单的音频录制示例:
import UIKit
import AVFoundation
class AudioRecorderViewController: UIViewController {
var audioRecorder: AVAudioRecorder?
var recordingURL: URL?
override func viewDidLoad() {
super.viewDidLoad()
// 设置音频会话
do {
try AVAudioSession.sharedInstance().setCategory(.record)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("音频会话设置失败: \(error)")
}
// 创建录制按钮
let recordButton = UIButton(frame: CGRect(x: 100, y: 200, width: 100, height: 50))
recordButton.setTitle("开始录制", for: .normal)
recordButton.addTarget(self, action: #selector(startRecording), for: .touchUpInside)
view.addSubview(recordButton)
// 创建停止按钮
let stopButton = UIButton(frame: CGRect(x: 250, y: 200, width: 100, height: 50))
stopButton.setTitle("停止录制", for: .normal)
stopButton.addTarget(self, action: #selector(stopRecording), for: .touchUpInside)
stopButton.isEnabled = false
view.addSubview(stopButton)
}
@objc func startRecording() {
let recordingSettings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100.0,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
recordingURL = documentsDirectory.appendingPathComponent("recording.m4a")
do {
audioRecorder = try AVAudioRecorder(url: recordingURL!, settings: recordingSettings)
audioRecorder?.delegate = self
audioRecorder?.record()
// 更新按钮状态
view.viewWithTag(1)?.isEnabled = false
view.viewWithTag(2)?.isEnabled = true
} catch {
print("录制初始化失败: \(error)")
}
}
@objc func stopRecording() {
audioRecorder?.stop()
// 更新按钮状态
view.viewWithTag(1)?.isEnabled = true
view.viewWithTag(2)?.isEnabled = false
}
}
extension AudioRecorderViewController: AVAudioRecorderDelegate {
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if flag {
print("录制成功,文件路径: \(recorder.url)")
} else {
print("录制失败")
}
}
}
10. 音频处理
除了播放和录制音频,还可以对音频进行处理,如音频剪辑、混音等。以下是一个简单的音频剪辑示例:
import AVFoundation
func trimAudio(inputURL: URL, outputURL: URL, startTime: TimeInterval, endTime: TimeInterval) {
let asset = AVAsset(url: inputURL)
let composition = AVMutableComposition()
let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
do {
let assetTrack = asset.tracks(withMediaType: .audio).first!
try audioTrack?.insertTimeRange(CMTimeRange(start: CMTime(seconds: startTime, preferredTimescale: 1000), duration: CMTime(seconds: endTime - startTime, preferredTimescale: 1000)), of: assetTrack, at: .zero)
} catch {
print("音频剪辑失败: \(error)")
return
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
exporter?.outputURL = outputURL
exporter?.outputFileType = .m4a
exporter?.exportAsynchronously(completionHandler: {
switch exporter?.status {
case .completed:
print("音频剪辑成功,文件路径: \(outputURL)")
case .failed:
if let error = exporter?.error {
print("音频导出失败: \(error)")
}
default:
break
}
})
}
总结
本文全面介绍了 iOS 开发中活动视图、扩展以及音频相关的知识。在活动视图方面,涵盖了
UIActivity
与视图控制器的交互,以及
SFSafariViewController
代理方法的使用。扩展部分详细讲解了动作扩展和分享扩展的编写步骤、调试方法。音频部分则从系统声音的播放,到音频的播放、录制和处理,为开发者提供了丰富的实现思路和代码示例。掌握这些知识,有助于开发者开发出功能更加丰富、交互更加友好的 iOS 应用。
以下是整个开发流程的 mermaid 流程图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(开始):::process --> B(UIActivity与视图控制器交互):::process
B --> C(SFSafariViewController代理方法):::process
C --> D(动作扩展):::process
D --> E(扩展调试):::process
E --> F(分享扩展):::process
F --> G(音频基础):::process
G --> H(系统声音):::process
H --> I(音频播放与控制):::process
I --> J(音频录制):::process
J --> K(音频处理):::process
K --> L(结束):::process
通过这个流程图,可以清晰地看到整个开发过程的各个环节和顺序,帮助开发者更好地理解和应用这些知识。
超级会员免费看
2703

被折叠的 条评论
为什么被折叠?



