iOS/APIExample-Audio/.agent/skills/upsert-case/SKILL.md
Add a new audio API demo case or modify an existing one in the APIExample-Audio project. Uses AgoraAudio_iOS SDK — no video APIs available. Covers folder creation, Entry/Main Swift file, storyboard, MenuItem registration, and Case Index update.
npx skillsauth add agoraio/api-examples upsert-caseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Examples/Basic/ or Examples/Advanced/Before adding, search the Case Index in ARCHITECTURE.md to confirm the case does not already exist.
Audio-only constraint: this project uses
AgoraAudio_iOSSDK. The video module is not available. Do NOT add any video API calls. See the NEVER list below.
| Scenario | Files |
|----------|-------|
| Add new case | New folder + .swift file + .storyboard, ViewController.swift (MenuItem), ARCHITECTURE.md (Case Index) |
| Modify existing case | Existing .swift file(s), optionally .storyboard, ARCHITECTURE.md (Case Index) |
APIExample-Audio/Examples/[Basic|Advanced]/<ExampleName>/
Create <ExampleName>.swift with Entry and Main classes:
import UIKit
import AgoraRtcKit
class <ExampleName>Entry: UIViewController {
@IBOutlet weak var channelTextField: UITextField!
@IBAction func onJoinPressed(_ sender: UIButton) {
guard let channelName = channelTextField.text, !channelName.isEmpty else { return }
let storyboard = UIStoryboard(name: "<ExampleName>", bundle: nil)
guard let mainVC = storyboard.instantiateViewController(
withIdentifier: "<ExampleName>") as? <ExampleName>Main else { return }
mainVC.configs = ["channelName": channelName]
navigationController?.pushViewController(mainVC, animated: true)
}
}
class <ExampleName>Main: BaseViewController {
var agoraKit: AgoraRtcEngineKit?
override func viewDidLoad() {
super.viewDidLoad()
guard let channelName = configs["channelName"] as? String else { return }
let config = AgoraRtcEngineConfig()
config.appId = KeyCenter.AppId
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
agoraKit?.setAudioProfile(.default)
// request microphone permission, then join
NetworkManager.shared.generateToken(channelName: channelName) { [weak self] token in
let option = AgoraRtcChannelMediaOptions()
option.publishMicrophoneTrack = true
self?.agoraKit?.joinChannel(byToken: token, channelId: channelName,
uid: 0, mediaOptions: option)
}
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
agoraKit?.leaveChannel()
AgoraRtcEngineKit.destroy()
}
}
}
extension <ExampleName>Main: AgoraRtcEngineDelegate {
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String,
withUid uid: UInt, elapsed: Int) {
LogUtils.log(message: "Joined: \(channel) uid: \(uid)", level: .info)
}
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
LogUtils.log(message: "Error: \(errorCode.rawValue)", level: .error)
}
}
Create APIExample-Audio/Base.lproj/<ExampleName>.storyboard with two scenes:
| Scene | Storyboard ID | Class |
|-------|--------------|-------|
| Entry | EntryViewController | <ExampleName>Entry |
| Main | <ExampleName> | <ExampleName>Main |
UI should contain only audio controls — no video rendering views.
Add to the menus array in APIExample-Audio/ViewController.swift:
MenuItem(name: "<Display Name>".localized,
storyboard: "<ExampleName>",
controller: "<ExampleName>")
Add a row to the ## Case Index table in ARCHITECTURE.md:
| <ExampleName> | `Examples/[Basic|Advanced]/<ExampleName>/<ExampleName>.swift` | `keyApi1()`, `keyApi2()` | One-line description |
BaseViewControllerViewController.swiftleaveChannel() + AgoraRtcEngineKit.destroy() called in willMove(toParent:) when parent == nilDispatchQueue.mainjoinChannel()ARCHITECTURE.mdenableVideo(), setupLocalVideo(), setupRemoteVideo(), or startPreview() — the SDK has no video moduleAgoraRtcVideoCanvas or VideoView to any storyboard or code in this projectAgoraRtcEngineKit in the Entry VCleaveChannel or destroy in viewDidDisappear — use willMove(toParent:) with parent == nilAgoraRtcEngineDelegate callbacks — always DispatchQueue.main.async { }AgoraRtcEngineKit instance between casesARCHITECTURE.mddevelopment
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including dialog class structure, message map registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
development
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including file structure, registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.