You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Objective-C实现iOS通信通知:图标显示为附件而非通知图标求助

iOS通信通知图标显示异常的Objective-C解决方案需求

我正在尝试使用Objective-C实现iOS通信通知功能,但无论采用何种写法,通知图标始终以附件形式呈现,而非作为通知图标正常显示。有趣的是,使用Swift实现相同逻辑时无此问题。经排查,核心差异在于Objective-C中没有Swift的content.updating(from: intent)对应的等效方法。由于onesignal-expo-plugin基于Objective-C开发,急需Objective-C下的解决方案。

现有Objective-C代码

#import <OneSignalFramework/OneSignalFramework.h>
#import <Intents/Intents.h>
#import <UserNotifications/UserNotifications.h>
#import "NotificationService.h"

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNNotificationRequest *receivedRequest;
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (INSendMessageIntent *)genMessageIntentFromRequest:(UNNotificationRequest *)request {
    NSDictionary *custom = request.content.userInfo[@"custom"];
    if (![custom isKindOfClass:[NSDictionary class]]) {
        return nil;
    }

    NSDictionary *a = custom[@"a"];
    if (![a isKindOfClass:[NSDictionary class]]) {
        return nil;
    }

    NSString *name = a[@"name"];
    NSString *urlString = a[@"url"];
    if (!name || !urlString) {
        return nil;
    }

    NSURL *url = [NSURL URLWithString:urlString];
    if (!url) {
        return nil;
    }

    INPersonHandle *handle = [[INPersonHandle alloc] initWithValue:nil type:INPersonHandleTypeUnknown];
    INImage *avatar = [INImage imageWithURL:url];
    INPerson *sender = [[INPerson alloc] initWithPersonHandle:handle
                                               nameComponents:nil
                                                  displayName:name
                                                        image:avatar
                                            contactIdentifier:nil
                                             customIdentifier:nil];

    if (@available(iOSApplicationExtension 14.0, *)) {
        return [[INSendMessageIntent alloc] initWithRecipients:nil
                                            outgoingMessageType:INOutgoingMessageTypeOutgoingMessageText
                                                        content:nil
                                              speakableGroupName:nil
                                           conversationIdentifier:nil
                                                     serviceName:nil
                                                          sender:sender
                                                     attachments:nil];
    } else {
        // Fallback on earlier versions
        return nil;
    }
}

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.receivedRequest = request;
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    NSDictionary *userInfo = request.content.userInfo;
    NSLog(@"Payload: %@", userInfo);
    
    if (@available(iOSApplicationExtension 15.0, *)) {
        INSendMessageIntent *intent = [self genMessageIntentFromRequest:request];
        if (intent) {
            INInteraction *interaction = [[INInteraction alloc] initWithIntent:intent response:nil];
            interaction.direction = INInteractionDirectionIncoming;

            [interaction donateInteractionWithCompletion:^(NSError * _Nullable error) {
                if (error == nil) {
                    if (self.bestAttemptContent) {
                        self.bestAttemptContent.title = intent.sender.displayName;
                        self.bestAttemptContent.subtitle = self.bestAttemptContent.title;
                        self.bestAttemptContent.body = self.bestAttemptContent.body;

                        NSString *urlString = userInfo[@"custom"][@"a"][@"url"];
                        NSURL *imageURL = [NSURL URLWithString:urlString];
                        [self downloadImageFromURL:imageURL completion:^(UNNotificationAttachment *attachment) {
                            if (attachment) {
                                self.bestAttemptContent.attachments = @[attachment];
                            }
                            [self forwardRequestToExtension];
                        }];
                    } else {
                        [self forwardRequestToExtension];
                    }
                } else {
                    [self forwardRequestToExtension];
                }
            }];
        } else {
            [self forwardRequestToExtension];
        }
    } else {
        [self forwardRequestToExtension];
    }
}

- (void)downloadImageFromURL:(NSURL *)url completion:(void (^)(UNNotificationAttachment *))completion {
    NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (data && !error) {
            NSString *tempDirectory = NSTemporaryDirectory();
            NSString *imagePath = [tempDirectory stringByAppendingPathComponent:@"image.jpg"];
            [data writeToFile:imagePath atomically:YES];
            NSURL *imageURL = [NSURL fileURLWithPath:imagePath];
            UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"media" URL:imageURL options:nil error:nil];
            completion(attachment);
        } else {
            completion(nil);
        }
    }];
    [downloadTask resume];
}

- (void)forwardRequestToExtension {
    if (self.receivedRequest && self.bestAttemptContent) {
        [OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest
                              withMutableNotificationContent:self.bestAttemptContent
                                          withContentHandler:self.contentHandler];
    }
}

- (void)serviceExtensionTimeWillExpire {
    if (self.contentHandler && self.bestAttemptContent) {
        [OneSignal serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];
        self.contentHandler(self.bestAttemptContent);
    }
}

@end

对比用的Swift代码

import UserNotifications
import Intents
import OneSignalExtension

class NotificationService: UNNotificationServiceExtension {
    
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var receivedRequest: UNNotificationRequest?
    private var bestAttemptContent: UNMutableNotificationContent?

    private func genMessageIntent(from request: UNNotificationRequest) -> INSendMessageIntent? {
        guard let custom = request.content.userInfo["custom"] as? [String: Any],
              let a = custom["a"] as? [String: Any],
              let name = a["name"] as? String, // Name that will appear
              let urlString = a["url"] as? String, // Photo that will appear
              let url = URL(string: urlString) else {
            return nil
        }

        let handle = INPersonHandle(value: nil, type: .unknown)
        let avatar = INImage(url: url)
        let sender = INPerson(personHandle: handle, nameComponents: nil, displayName: name, image: avatar, contactIdentifier: nil, customIdentifier: nil)

      if #available(iOSApplicationExtension 14.0, *) {
        return INSendMessageIntent(
          recipients: nil,
          outgoingMessageType: .outgoingMessageText,
          content: nil,
          speakableGroupName: nil,
          conversationIdentifier: nil,
          serviceName: nil,
          sender: sender,
          attachments: nil
        )
      } else {
        // Fallback on earlier versions
        return nil
      }
    }
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.receivedRequest = request
        self.contentHandler = contentHandler
        self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
      
      let userInfo = request.content.userInfo
      print("Payload:", userInfo)

      
      if #available(iOSApplicationExtension 15.0, *) {
        guard let intent = genMessageIntent(from: request) else {
          forwardRequestToExtension()
          return
        }
        
        let interaction = INInteraction(intent: intent, response: nil)
        interaction.direction = .incoming
        
        interaction.donate { [weak self] error in
          guard let self = self, error == nil else { return }
          
          do {
            let content = try request.content.updating(from: intent)
            self.bestAttemptContent = (content.mutableCopy() as? UNMutableNotificationContent)
            self.forwardRequestToExtension()
          } catch {
            // Handle errors appropriately
          }
        }
      } else {
        forwardRequestToExtension()
      }
    }

    private func forwardRequestToExtension() {
        guard let receivedRequest = receivedRequest, let bestAttemptContent = bestAttemptContent else { return }
        OneSignalExtension.didReceiveNotificationExtensionRequest(receivedRequest, with: bestAttemptContent, withContentHandler: contentHandler)
    }
    
    override func serviceExtensionTimeWillExpire() {
        guard let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent else { return }
        OneSignalExtension.serviceExtensionTimeWillExpireRequest(receivedRequest!, with: bestAttemptContent)
        contentHandler(bestAttemptContent)
    }
}

内容的提问来源于stack exchange,提问作者romants

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 23:35:56