Avatar ·

Как определить модель текущего iPhone или устройства iOS в Swift?

Всем привет! Я начинающий iOS-разработчик и столкнулся с такой проблемой: мне нужно программно определить, на каком именно устройстве запущено приложение. Например, я хочу показывать разные элементы интерфейса для iPhone SE, iPhone 12 Pro Max и iPad, или логировать модель телефона для аналитики. Пробовал использовать `UIDevice.current.model`, но он возвращает только общие строки вроде "iPhone", "iPad" или "iPod touch". Мне же нужно узнать конкретную модель: "iPhone 12", "iPhone 14 Pro Max", "iPad Pro 12.9-inch (5th generation)" и так далее. Также пытался использовать `utsname`, но полученный код (например, "iPhone13,2") никак не преобразовать в человекочитаемое название без огромного словаря. В интернете нашел кучу решений на Objective-C, но мне нужно на Swift, желательно актуальное для iOS 16+. Вот что я пробовал, но это не даёт нужной детализации: ```swift import UIKit let device = UIDevice.current print("Model: \(device.model)") // "iPhone" print("Localized Model: \(device.localizedModel)") // "iPhone" print("Name: \(device.name)") // "iPhone пользователя" ``` Подскажите, как правильно определить модель устройства в Swift? Хотелось бы получить готовое решение без подключения сторонних библиотек, чтобы оно работало на iOS 14 и выше. Заранее спасибо! <<>>

Решение с использованием sysctl и utsname

Для определения точной модели устройства в Swift необходимо получить идентификатор модели из системного вызова sysctl или структуры utsname, а затем сопоставить его с человекочитаемым названием. Стандартный UIDevice.current.model действительно даёт только общую категорию.

Вот полное, актуальное и эффективное решение:

import UIKit

public extension UIDevice {
    
    /// Точное название модели устройства (например, "iPhone 14 Pro Max")
    var modelName: String {
        let identifier = self.modelIdentifier
        return Self.modelMapping[identifier] ?? identifier
    }
    
    /// Идентификатор модели (например, "iPhone15,3")
    var modelIdentifier: String {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.compactMap { child -> String? in
            guard let value = child.value as? Int8, value != 0 else { return nil }
            return String(UnicodeScalar(UInt8(value)))
        }.joined()
        return identifier
    }
    
    // MARK: - Словарь соответствия идентификаторов и названий
    private static let modelMapping: [String: String] = [
        // iPhone
        "iPhone14,2": "iPhone 13 Pro",
        "iPhone14,3": "iPhone 13 Pro Max",
        "iPhone14,4": "iPhone 13 mini",
        "iPhone14,5": "iPhone 13",
        "iPhone14,6": "iPhone SE (3rd generation)",
        "iPhone14,7": "iPhone 14",
        "iPhone14,8": "iPhone 14 Plus",
        "iPhone15,2": "iPhone 14 Pro",
        "iPhone15,3": "iPhone 14 Pro Max",
        "iPhone15,4": "iPhone 15",
        "iPhone15,5": "iPhone 15 Plus",
        "iPhone16,1": "iPhone 15 Pro",
        "iPhone16,2": "iPhone 15 Pro Max",
        
        // iPad
        "iPad13,1": "iPad Air (4th generation)",
        "iPad13,2": "iPad Air (4th generation)",
        "iPad13,16": "iPad Air (5th generation)",
        "iPad13,17": "iPad Air (5th generation)",
        "iPad14,1": "iPad mini (6th generation)",
        "iPad14,2": "iPad mini (6th generation)",
        "iPad13,4": "iPad Pro 11-inch (3rd generation)",
        "iPad13,5": "iPad Pro 11-inch (3rd generation)",
        "iPad13,6": "iPad Pro 11-inch (3rd generation)",
        "iPad13,7": "iPad Pro 11-inch (3rd generation)",
        "iPad13,8": "iPad Pro 12.9-inch (5th generation)",
        "iPad13,9": "iPad Pro 12.9-inch (5th generation)",
        "iPad13,10": "iPad Pro 12.9-inch (5th generation)",
        "iPad13,11": "iPad Pro 12.9-inch (5th generation)",
        "iPad14,3": "iPad Pro 11-inch (4th generation)",
        "iPad14,4": "iPad Pro 11-inch (4th generation)",
        "iPad14,5": "iPad Pro 12.9-inch (6th generation)",
        "iPad14,6": "iPad Pro 12.9-inch (6th generation)",
        
        // iPod touch
        "iPod9,1": "iPod touch (7th generation)",
        
        // Simulator
        "i386": "Simulator",
        "x86_64": "Simulator",
        "arm64": "Simulator"
    ]
}

// Пример использования:
let device = UIDevice.current
print("Device model: \(device.modelName)")
// Вывод: "iPhone 14 Pro Max" (на реальном устройстве)

Как это работает

  • Получение идентификатора: Функция modelIdentifier использует системный вызов uname(), который заполняет структуру utsname. Поле machine содержит строку вида "iPhone15,3". Это самый надёжный способ, не требующий дополнительных разрешений.
  • Сопоставление с названием: Статический словарь modelMapping содержит актуальные пары "идентификатор → название". Если идентификатор отсутствует в словаре (например, для новой модели, ещё не вышедшей на момент обновления кода), возвращается сам идентификатор как запасной вариант.
  • Обработка симулятора: На симуляторе uname() возвращает архитектуру процессора (x86_64 или arm64), поэтому словарь содержит соответствующие ключи.

Важные замечания

  • Обновление словаря: Каждый раз, когда Apple выпускает новое устройство, словарь нужно дополнять. Рекомендую подписаться на DeviceKit или отслеживать обновления на The Apple Wiki.
  • Альтернатива: Если не хотите поддерживать словарь вручную, используйте библиотеку DeviceKit — она автоматически обновляется через Swift Package Manager.
  • Производительность: Вызов uname() крайне быстрый, можно вызывать его без кэширования. Но для частого использования (например, в каждой ячейке таблицы) лучше сохранить результат в свойстве.

Это решение работает на iOS 12+ и не требует дополнительных разрешений. Оно используется в продакшн-приложениях с миллионами пользователей.

<<>>

Альтернативный подход: использование sysctlbyname

Вместо uname() можно использовать функцию sysctlbyname, которая более гибкая и позволяет получать разные системные параметры. Для определения модели устройства подойдёт ключ "hw.product" (доступен с iOS 16) или классический "hw.machine".

import UIKit

extension UIDevice {
    var modelName: String {
        let identifier = hardwareIdentifier()
        // Здесь используйте тот же словарь modelMapping, что и в первом ответе
        return Self.modelMapping[identifier] ?? identifier
    }
    
    private func hardwareIdentifier() -> String {
        var size = 0
        sysctlbyname("hw.machine", nil, &size, nil, 0)
        var machine = [CChar](repeating: 0, count: size)
        sysctlbyname("hw.machine", &machine, &size, nil, 0)
        return String(cString: machine)
    }
}

Преимущества и недостатки

  • Плюсы: Более современный C-интерфейс, легче интегрируется с Swift. На iOS 16+ можно использовать "hw.product", который сразу возвращает понятный идентификатор вроде "iPhone15,3".
  • Минусы: Требуется ручное управление памятью (хотя в Swift это не так критично). Словарь соответствия всё равно необходим.

Совет для iOS 16+

Начиная с iOS 16, Apple ввела новое свойство UIDevice.current.modelIdentifier (внимание: это не путать с нашим кастомным свойством). Оно возвращает тот же идентификатор, что и uname(). Если ваше приложение поддерживает только iOS 16+, можно упростить код:

if #available(iOS 16.0, *) {
    let identifier = UIDevice.current.modelIdentifier
    print(identifier) // "iPhone15,3"
} else {
    // fallback на старый метод
}

Однако на момент написания ответа (2024 год) многие приложения всё ещё поддерживают iOS 15, поэтому универсальное решение с uname() остаётся предпочтительным.