iOS/APIExample-OC/.agent/skills/upsert-case/SKILL.md
Add a new API demo case or modify an existing one in the APIExample-OC (Objective-C + UIKit) project. Covers folder creation, Entry/Main OC files, 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.
| Scenario | Files |
|----------|-------|
| Add new case | New folder + .h/.m files + .storyboard, ViewController.m (MenuItem), ARCHITECTURE.md (Case Index) |
| Modify existing case | Existing .h/.m files, optionally .storyboard, ARCHITECTURE.md (Case Index) |
APIExample-OC/Examples/[Basic|Advanced]/<ExampleName>/
Create <ExampleName>.h:
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface <ExampleName>Entry : UIViewController
@end
@interface <ExampleName>Main : BaseViewController
@end
NS_ASSUME_NONNULL_END
Create <ExampleName>.m:
#import "<ExampleName>.h"
#import <AgoraRtcKit/AgoraRtcKit.h>
#import "KeyCenter.h"
#import "NetworkManager.h"
@interface <ExampleName>Entry ()
@property (weak, nonatomic) IBOutlet UITextField *channelTextField;
@end
@implementation <ExampleName>Entry
- (IBAction)onJoinPressed:(UIButton *)sender {
NSString *channelName = self.channelTextField.text;
if (channelName.length == 0) return;
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"<ExampleName>" bundle:nil];
<ExampleName>Main *mainVC = [sb instantiateViewControllerWithIdentifier:@"<ExampleName>"];
mainVC.configs = @{@"channelName": channelName};
[self.navigationController pushViewController:mainVC animated:YES];
}
@end
@interface <ExampleName>Main () <AgoraRtcEngineDelegate>
@property (nonatomic, strong) AgoraRtcEngineKit *agoraKit;
@end
@implementation <ExampleName>Main
- (void)viewDidLoad {
[super viewDidLoad];
NSString *channelName = self.configs[@"channelName"];
AgoraRtcEngineConfig *config = [AgoraRtcEngineConfig new];
config.appId = [KeyCenter AppId];
self.agoraKit = [AgoraRtcEngineKit sharedEngineWithConfig:config delegate:self];
// configure engine, request permissions, then join
[[NetworkManager shared] generateTokenWithChannelName:channelName success:^(NSString *token) {
AgoraRtcChannelMediaOptions *option = [AgoraRtcChannelMediaOptions new];
[self.agoraKit joinChannelByToken:token channelId:channelName
uid:0 mediaOptions:option joinSuccess:nil];
}];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (self.isMovingFromParentViewController) {
[self.agoraKit leaveChannel:nil];
[AgoraRtcEngineKit destroy];
}
}
@end
@implementation <ExampleName>Main (AgoraRtcEngineDelegate)
- (void)rtcEngine:(AgoraRtcEngineKit *)engine didJoinChannel:(NSString *)channel
withUid:(NSUInteger)uid elapsed:(NSInteger)elapsed {
NSLog(@"Joined: %@ uid: %lu", channel, (unsigned long)uid);
}
- (void)rtcEngine:(AgoraRtcEngineKit *)engine didOccurError:(AgoraErrorCode)errorCode {
NSLog(@"Error: %ld", (long)errorCode);
}
@end
Create APIExample-OC/<ExampleName>.storyboard with two scenes:
| Scene | Storyboard ID | Class |
|-------|--------------|-------|
| Entry | EntryViewController | <ExampleName>Entry |
| Main | <ExampleName> | <ExampleName>Main |
Add to +[MenuSection menus] in ViewController.m:
[[MenuItem alloc] initWithName:NSLocalizedString(@"<Display Name>", nil)
storyboard:@"<ExampleName>"
controller:@""]
Add a row to the ## Case Index table in ARCHITECTURE.md:
| <ExampleName> | `Examples/[Basic|Advanced]/<ExampleName>/<ExampleName>.m` | `keyApi1:`, `keyApi2:` | One-line description |
.h and .m files created with Entry and Main classesBaseViewController and conforms to AgoraRtcEngineDelegateViewController.mleaveChannel: + [AgoraRtcEngineKit destroy] called when leavingdispatch_async(dispatch_get_main_queue(), ^{ })__weak typeof(self) weakSelf = self used in blocks that capture selfjoinChannelByToken:ARCHITECTURE.mdAgoraRtcEngineKit in the Entry VC__unsafe_unretained for delegate references — use __weakAgoraRtcEngineDelegate callbacks — always dispatch_async(dispatch_get_main_queue(), ^{ })Main.storyboard — each case must have its own .storyboard fileAgoraRtcEngineKit instance between casesjoinChannelByToken: before requesting camera/microphone permissionsARCHITECTURE.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.