Accessing an iOS URL Scheme, at Runtime - ios

This is fairly straightforward.
I have an app that publishes a URL scheme, like so.
In the spirit of DRY, I'd like to avoid referencing it, using constant strings. Instead, I'd like to fetch it from the bundle.
How do I do that?

This snippet prints the URL schemes defined in an app's Info.plist:
if let types = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]] {
var result = [String]()
for type in types {
guard let schemes = type["CFBundleURLSchemes"] as? [String] else { continue }
guard let scheme = schemes.first else { continue }
result.append(scheme)
}
print(result)
}

This was solved by leveraging Gereon's answer. Here's how I did it:
/* ###################################################################################################################################### */
// MARK: - Fleshing out the Addressable (General) -
/* ###################################################################################################################################### */
public extension LGV_MeetingSDK_AddressableEntity_Protocol {
/* ################################################################## */
/**
This fetches the first URL scheme from the bundle, renders it as a String, and returns it.
*/
var urlScheme: String {
guard let bundleTypes = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]],
let myURLScheme = (bundleTypes.first?["CFBundleURLSchemes"] as? [String])?.first
else { return "" }
return myURLScheme
}
}
/* ###################################################################################################################################### */
// MARK: - Fleshing out the Addressable (User) -
/* ###################################################################################################################################### */
public extension HeartOfRecovrr_Member {
/* ################################################################## */
/**
This returns an addressable URL for this member record.
*/
var urlString: String { "\(urlScheme)://user/\(id)" }
}
And here it is, in my general-purpose Swift extensions package.

Related

How to access & get nested values from IOS Swift 'Any' type?

I am trying to read from Firestore into a Dictionary[Any] type using Struct. I can get the values loaded into variable "data" dictionary with Any type.
However I cannot loop thru it to access normal nested Dictionary variable.
I cannot get Key, values printed.
Following is my code:
class PullQuestions {
//shared instance variable
**public var data = [Any]()**
private var qdb = Firestore.firestore()
public struct questionid
{
let qid : String
var questions : [basequestion]
var answers: [baseans]
}
public struct basequestion {
let category : String
let question : String
}
public struct baseans {
let answer : String
}
class var sharedManager: PullQuestions {
struct Static {
static let instance = PullQuestions()
}
return Static.instance
}
static func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = PullQuestions.sharedManager.qdb.collection("questions")
//var data = [Any]()
rootCollection.order(by: "upvote", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
// var questiondoc = [basequestion]()
for questioncollection in topSnapshot {
rootCollection.document(questioncollection.documentID).collection("answers").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var answers = [baseans]()
for document in snapshot { //There should be only one Document for each answer collection
//Read thru all fields
for i in 0..<document.data().count
{
let newAns = baseans(answer: answer)
print("Answer Docs=>", (answer))
answers.append(newAns)
}
}
let qid = questioncollection.documentID
let category = questioncollection.data()["category"] as! String
let question = questioncollection.data()["question"] as! String
let newQuestions = basequestion(category: category ,question: question)
let newQuestionDict = questionid(qid: qid, questions: [newQuestions], answers: answers)
PullQuestions.sharedManager.data.append(newQuestionDict)
//Return data on completion
completion(PullQuestions.sharedManager.data)
})
}
}
})
}
}
I can print like this
print("Count =>", (PullQuestions.sharedManager.data.count))
// print(PullQuestions.sharedManager.data.first ?? "Nil")
print(PullQuestions.sharedManager.data[0])
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
I could access only the key.. how do i go and get the nested values ?
First of all, consider using Swift code conventions (e.g. your structs are named with small letters, but you should start with capital), this will make your code more readable.
Returning to your question. You use an array instead of dictionary (this piece of code: public var data = [Any]()). And here you are trying to print values:
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
In this context element is an Any object, thus you cannot access any underlying properties. In order to do this you have two options:
1. You should specify the type of array's objects in it's declaration like this:
public var data = [questionid]()
or you can user this:
public var data: [questionid] = []
These two are equals, use the one you prefer.
2. If for any reasons you don't want to specify the type in declaration, you can cast it in your loop. Like this:
for element in PullQuestions.sharedManager.data
{
if let element = element as? quetionid {
print("Elements in data:=>", (element))
// you can also print element.qid, element.questions, element.answers
} else {
print("Element is not questionid")
}
}
You could of course use the force cast:
let element = element as! questionid
and avoid if let syntax (or guard let if you prefer), but I wouldn't recommend this, because it (potentially) can crash your app if element will be nil or any other type.

Cannot subscript a value of type '[HTTPCookiePropertyKey : Any]' with an index of type 'String'

Error: Cannot subscript a value of type [HTTPCookiePropertyKey : Any] with an index of type String
Code:
func copyCookiesForEx() {
if let cookies = HTTPCookieStorage.shared.cookies(for: NSURL(string: kEHentaiURL)! as URL) {
print(cookies)
for c in cookies {
if var properties = c.properties {
properties["Domain"] = ".exhentai.org" -< error is here
if let newCookie = HTTPCookie(properties: properties) {
HTTPCookieStorage.shared.setCookie(newCookie)
}
}
}
}
}
The error message states clearly what the issue is. properties is of type Dictionary<HTTPCookiePropertyKey,Any>, so you can't use a String as a key for subscripting.
You have to use HTTPCookiePropertyKey.domain instead of "domain".
You also shouldn't use NSURL(string:) and then cast it to URL when you can simply use the same init method of URL directly.
func copyCookiesForEx() {
if let cookies = HTTPCookieStorage.shared.cookies(for: URL(string: kEHentaiURL)!) {
if var properties = c.properties {
properties[.domain] = ".extentai.org" // HTTPCookiePropertyKey.domain
if let newCookie = HTTPCookie(properties: properties) {
HTTPCookieStorage.shared.setCookie(newCookie)
}
}
}
}
Swift 4 introduces a lot of new key structs for reliability and robustness.
It's simply
properties[.domain] = ".exhentai.org"
Please read the documentation of HTTPCookiePropertyKey
And don't use NSURL with ugly cast to URL in Swift 3+, use always native struct URL
... cookies(for:URL(string: kEHentaiURL)!) { ...

How to handle SignalProducer with ReactiveSwift and Firebase asynchronous method calls?

I am working on an iOS App with Swift 3 using ReactiveSwift 1.1.1, the MVVM + Flow Coordinator pattern and Firebase as a backend. I only recently started to adapt to FRP and I am still trying to figure out how to integrate new functionalities into my existing code base.
For instance, my model uses a asynchronous method from Firebase to download thumbnails from the web and I want to provide a SignalProducer<Content, NoError> to subscribe from my ViewModel classes and observe, if thumbnails have been downloaded, which then updates the UI.
// field to be used from the view-models to observe
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
// TODO: send next content via completion below
}
// thumbnail download method
public func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
debugPring("Error id")
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
debugPrint("Error download")
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// TODO: emit signal with content
// How to send the content via the SignalProducer above?
})
}
I have also tried something similar with Signal<Content, NoError>, whereas I used the Signal<Content, NoError>.pipe() method to receive a (observer, disposable) tuple and I saved the observer as a private global field to access it form the Firebase callback.
Questions:
Is this the right approach or am I missing something?
How do I emit the content object on completion?
UPDATE:
After some hours of pain, I found out how to design the SingalProducer to emit signals and to subscribe from the ViewModels.
Maybe the following code snippet will help also others:
// model protocol
import ReactiveSwift
import enum Result.NoError
public protocol ContentService {
func findThumbnail(bucketId: String, contentId: String)
var thumbnailContentProducer: SignalProducer<Content, NoError> { get }
}
// model implementation using firebase
import Firebase
import FirebaseStorage
import ReactiveSwift
public class FirebaseContentService: ContentService {
// other fields, etc.
// ...
private var thumbnailContentObserver: Observer<Content, NoError>?
private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>?
var thumbnailContentProducer: SignalProducer<Content, NoError> {
return thumbnailContentSignalProducer!
}
init() {
thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
self.thumbnailContentObserver = observer
}
}
func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
// TODO handle error
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
// TODO handle error
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// emit signal
self.thumbnailContentObserver?.send(value: content)
})
}
}
// usage from a ViewModel
contentService.thumbnailContentProducer
.startWithValues { content in
self.contents.append(content)
}
Maybe someone can verify the code above and say that this is the right way to do it.
I think you were on the right path when you were looking at using Signal with pipe. The key point is that you need to create a new SignalProducer for each thumbnail request, and you need a way to combine all of those requests into one resulting signal. I was thinking something like this (note this is untested code, but it should get the idea across):
class FirebaseContentService {
// userService and storageThumbnail defined here
}
extension FirebaseContentService: ReactiveExtensionsProvider { }
extension Reactive where Base: FirebaseContentService {
private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> {
return SignalProducer<Content, ContentError> { (observer, disposable) in
guard let userId = self.base.userService.getCurrentUserId() else {
observer.send(error: ContentError.invalidUserLogin)
return
}
let ref = self.base.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
observer.send(error: ContentError.contentNotFound)
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
observer.send(value: content)
observer.sendCompleted()
})
}
}
}
class ThumbnailProvider {
public let thumbnailSignal: Signal<Content, NoError>
private let input: Observer<(bucketId: String, contentId: String), NoError>
init(contentService: FirebaseContentService) {
let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe()
self.input = observer
self.thumbnailSignal = signal
.flatMap(.merge) { param in
return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId)
.flatMapError { error in
debugPrint("Error download")
return SignalProducer.empty
}
}
}
public func findThumbnail(bucketId: String, contentId: String) {
input.send(value: (bucketId: bucketId, contentId: contentId))
}
}
Using ReactiveExtensionsProvider like this is the idiomatic way of adding reactive APIs to existing functionality via a reactive property.
The actual requesting code is confined to getThumbnailContentSignalProducer which creates a SignalProducer for each request. Note that errors are passed along here, and the handling and conversion to NoError happens later.
findThumbnails just takes a bucketId and contentId and sends it through the input observable.
The construction of thumbnailSignal in init is where the magic happens. Each input, which is a tuple containing a bucketId and contentId, is converted into a request via flatMap. Note that the .merge strategy means the thumbnails are sent as soon as possible in whatever order the requests complete. You can use .concat if you want to ensure that the thumbnails are returned in the same order they were requested.
The flatMapError is where the potential errors get handled. In this case it's just printing "Error download" and doing nothing else.

Write Generic Swift Method to load data from property list

I have a method that loads an array of dictionaries from a propertylist. Then I change those arrays of dictionaries to array of a defined custom type;
I want to write that method in generic form so I call that method with the type I expect, then the method loads it and returns an array of my custom type rather than dictionaries
func loadPropertyList(fileName: String) -> [[String:AnyObject]]?
{
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")
{
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path)
{
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]]
{
return temp
}
}catch{}
}
}
return nil
}
//
func loadList<T>(fileName: String) -> [T]?{//**Here the answer I am expecting**}
I am assuming your function to read from a Plist works and that you don't want to subclass NSObject.
Since Swift reflecting does not support setting values this is not possible without some implementation for each Type you want this to work for.
It can however be done in a pretty elegant way.
struct PlistUtils { // encapsulate everything
static func loadPropertyList(fileName: String) -> [[String:AnyObject]]? {
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist") {
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path) {
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]] {
return temp
}
} catch {
return nil
}
}
}
return nil
}
}
This protocol will be used in a generic fashion to get the Type name and read the corresponding Plist.
protocol PListConstructible {
static func read() -> [Self]
}
This protocol will be used to implement Key Value setters.
protocol KeyValueSettable {
static func set(fromKeyValueStore values:[String:AnyObject]) -> Self
}
This is the combination of both to generate an array of objects. This does require that the Plist is named after the Type.
extension PListConstructible where Self : KeyValueSettable {
static func read() -> [Self] {
let name = String(reflecting: self)
var instances : [Self] = []
if let data = PlistUtils.loadPropertyList(name) {
for entry in data {
instances.append(Self.set(fromKeyValueStore: entry))
}
}
return instances
}
}
This is some Type.
struct Some : PListConstructible {
var alpha : Int = 0
var beta : String = ""
}
All you have to do is implement the Key Value setter and it will now be able to be read from a Plist.
extension Some : KeyValueSettable {
static func set(fromKeyValueStore values: [String : AnyObject]) -> Some {
var some = Some()
some.alpha = (values["alpha"] as? Int) ?? some.alpha
some.beta = (values["beta"] as? String) ?? some.beta
return some
}
}
This is how you use it.
Some.read()

Query Available iOS Disk Space with Swift

I'm trying to get the available iOS device storage using Swift. I found this function here
func deviceRemainingFreeSpaceInBytes() -> NSNumber {
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let systemAttributes = NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectoryPath.last as String, error: nil)
return systemAttributes[NSFileSystemFreeSize] as NSNumber
}
But at compile time this error is given: [NSObject : AnyObject]? does not have a member named 'subscript' I believe this error arises from the issue mentioned here, namely that attributesOfFileSystemForPath returns an optional dictionary (documentation). I understand the problem in a general sense, but because the suggested solution involves a nested case, I don't quite see how to fix the function I'm interested in (it doesn't help that I'm quite new to Swift). Can someone suggest how to make the function work? NOTE: I'm not sure if the original function was tested by the author or if it worked under an xcode 6 beta, but it doesn't work under the GM as far as I can see.
iOS 11 Update
The answers given below no longer provide accurate results under iOS 11. There are new volume capacity keys that can be passed to URL.resourceValues(forKeys:) that provide values that match what is available in device settings.
static let volumeAvailableCapacityKey: URLResourceKey
Key for the volume’s available capacity in bytes (read-only).
static let volumeAvailableCapacityForImportantUsageKey: URLResourceKey
Key for the volume’s available capacity in bytes for storing important resources (read-only).
static let volumeAvailableCapacityForOpportunisticUsageKey: URLResourceKey
Key for the volume’s available capacity in bytes for storing nonessential resources (read-only).
static let volumeTotalCapacityKey: URLResourceKey
Key for the volume’s total capacity in bytes (read-only).
From Apple's documentation:
Overview
Before you try to store a large amount of data locally, first verify that you have sufficient storage capacity. To get the storage capacity of a volume, you construct a URL (using an instance of URL) that references an object on the volume to be queried, and then query that volume.
Decide Which Query Type to Use
The query type to use depends on what's being stored. If you’re storing data based on a user request or resources the app requires to function properly (for example, a video the user is about to watch or resources that are needed for the next level in a game), query against volumeAvailableCapacityForImportantUsageKey. However, if you’re downloading data in a more predictive manner (for example, downloading a newly available episode of a TV series that the user has been watching recently), query against volumeAvailableCapacityForOpportunisticUsageKey.
Construct a Query
Use this example as a guide to construct your own query:
let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
do {
let values = try fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
if let capacity = values.volumeAvailableCapacityForImportantUsage {
print("Available capacity for important usage: \(capacity)")
} else {
print("Capacity is unavailable")
}
} catch {
print("Error retrieving capacity: \(error.localizedDescription)")
}
Original Answer
Optional binding with if let works here as well.
I would suggest that the function returns an optional Int64, so that it can return
nil to signal a failure:
func deviceRemainingFreeSpaceInBytes() -> Int64? {
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
if let systemAttributes = NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectoryPath.last as String, error: nil) {
if let freeSize = systemAttributes[NSFileSystemFreeSize] as? NSNumber {
return freeSize.longLongValue
}
}
// something failed
return nil
}
Swift 2.1 Update:
func deviceRemainingFreeSpaceInBytes() -> Int64? {
let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last!
guard
let systemAttributes = try? NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectory),
let freeSize = systemAttributes[NSFileSystemFreeSize] as? NSNumber
else {
// something failed
return nil
}
return freeSize.longLongValue
}
Swift 3.0 Update:
func deviceRemainingFreeSpaceInBytes() -> Int64? {
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!
guard
let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: documentDirectory),
let freeSize = systemAttributes[.systemFreeSize] as? NSNumber
else {
// something failed
return nil
}
return freeSize.int64Value
}
Usage:
if let bytes = deviceRemainingFreeSpaceInBytes() {
print("free space: \(bytes)")
} else {
print("failed")
}
Well, according to the above codes:
let usedSpace = totalDiskSpaceInBytes - freeDiskSpaceInBytes
you might find out that usedSpace doesn't equal the value of iPhone setting page. That is because in iOS11, Apple introduces Total available capacity in bytes for "Important" resources.
Total available capacity in bytes for "Important" resources, including
space expected to be cleared by purging non-essential and cached
resources. "Important" means something that the user or application
clearly expects to be present on the local system, but is ultimately
replaceable. This would include items that the user has explicitly
requested via the UI, and resources that an application requires in
order to provide functionality.
Examples: A video that the user
has explicitly requested to watch but has not yet finished watching or
an audio file that the user has requested to download.
This value
should not be used in determining if there is room for an
irreplaceable resource. In the case of irreplaceable resources, always
attempt to save the resource regardless of available capacity and
handle failure as gracefully as possible.
In order to get the exact same value as what we see in iPhone setting page, we can get free space by volumeAvailableCapacityForImportantUsage
if let space = try? URL(fileURLWithPath: NSHomeDirectory() as String).resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage {
return space ?? 0
}
You can use the following UIDevice extension:
Swift4
extension UIDevice {
func MBFormatter(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = ByteCountFormatter.Units.useMB
formatter.countStyle = ByteCountFormatter.CountStyle.decimal
formatter.includesUnit = false
return formatter.string(fromByteCount: bytes) as String
}
//MARK: Get String Value
var totalDiskSpaceInGB:String {
return ByteCountFormatter.string(fromByteCount: totalDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
}
var freeDiskSpaceInGB:String {
return ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
}
var usedDiskSpaceInGB:String {
return ByteCountFormatter.string(fromByteCount: usedDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
}
var totalDiskSpaceInMB:String {
return MBFormatter(totalDiskSpaceInBytes)
}
var freeDiskSpaceInMB:String {
return MBFormatter(freeDiskSpaceInBytes)
}
var usedDiskSpaceInMB:String {
return MBFormatter(usedDiskSpaceInBytes)
}
//MARK: Get raw value
var totalDiskSpaceInBytes:Int64 {
guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value else { return 0 }
return space
}
/*
Total available capacity in bytes for "Important" resources, including space expected to be cleared by purging non-essential and cached resources. "Important" means something that the user or application clearly expects to be present on the local system, but is ultimately replaceable. This would include items that the user has explicitly requested via the UI, and resources that an application requires in order to provide functionality.
Examples: A video that the user has explicitly requested to watch but has not yet finished watching or an audio file that the user has requested to download.
This value should not be used in determining if there is room for an irreplaceable resource. In the case of irreplaceable resources, always attempt to save the resource regardless of available capacity and handle failure as gracefully as possible.
*/
var freeDiskSpaceInBytes:Int64 {
if #available(iOS 11.0, *) {
if let space = try? URL(fileURLWithPath: NSHomeDirectory() as String).resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage {
return space ?? 0
} else {
return 0
}
} else {
if let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value {
return freeSpace
} else {
return 0
}
}
}
var usedDiskSpaceInBytes:Int64 {
return totalDiskSpaceInBytes - freeDiskSpaceInBytes
}
}
usage:
print("totalDiskSpaceInBytes: \(UIDevice.current.totalDiskSpaceInBytes)")
print("freeDiskSpace: \(UIDevice.current.freeDiskSpaceInBytes)")
print("usedDiskSpace: \(UIDevice.current.usedDiskSpaceInBytes)")
I have written a class to get available/used memory using Swift.
Demo at: https://github.com/thanhcuong1990/swift-disk-status
Upgrade to support Swift 3.
import UIKit
class DiskStatus {
//MARK: Formatter MB only
class func MBFormatter(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = ByteCountFormatter.Units.useMB
formatter.countStyle = ByteCountFormatter.CountStyle.decimal
formatter.includesUnit = false
return formatter.string(fromByteCount: bytes) as String
}
//MARK: Get String Value
class var totalDiskSpace:String {
get {
return ByteCountFormatter.string(fromByteCount: totalDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
}
}
class var freeDiskSpace:String {
get {
return ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
}
}
class var usedDiskSpace:String {
get {
return ByteCountFormatter.string(fromByteCount: usedDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
}
}
//MARK: Get raw value
class var totalDiskSpaceInBytes:Int64 {
get {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value
return space!
} catch {
return 0
}
}
}
class var freeDiskSpaceInBytes:Int64 {
get {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value
return freeSpace!
} catch {
return 0
}
}
}
class var usedDiskSpaceInBytes:Int64 {
get {
let usedSpace = totalDiskSpaceInBytes - freeDiskSpaceInBytes
return usedSpace
}
}
}
Demo:
This is similar to Martin's answer for Swift 3.1, but is converted to an extension of UIDevice to make accessing it easier.
extension UIDevice {
var systemSize: Int64? {
guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
let totalSize = (systemAttributes[.systemSize] as? NSNumber)?.int64Value else {
return nil
}
return totalSize
}
var systemFreeSize: Int64? {
guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
let freeSize = (systemAttributes[.systemFreeSize] as? NSNumber)?.int64Value else {
return nil
}
return freeSize
}
}
To get free space:
UIDevice.current.systemFreeSize
And to get total space:
UIDevice.current.systemSize

Resources