Introduction

This online notebook is intended to serve as an auxiliary documentation resource. It will be used to collate resources and documentation on a variety of topics in different languages, libraries, and frameworks. The primary objective of this resource is to bring together useful information that is obscure, old, or otherwise difficult to obtain. Such information may still be useful, though it may be fragmented and and spread over the vastness of the internet on multiple sites, such as forums and documentation archives. Where possible, usable examples of each topic will be included.

Swift

Swift is a general-purpose, multi-paradigm, compiled programming language developed by Apple Inc. for iOS, iPadOS, macOS, watchOS, tvOS, and Linux [1].

Useful pages

SO: Sorting arrays by boolean

SO: Object collision in SpriteKit

SO: Random numbers in Swift

SO: Storyboard and SwiftUI in the same project

SO: Combining ARKit and SwiftUI without Storyboard

SO: Recreating share sheet modal in iOS 13

Apple: SwiftUI Tutorials

Apple: Combining SwiftUI and UIKit

Apple: UIKit customisation

Other: Using SwiftUI with ARKit and SceneKit

Other: A best-in-class app

Other: iPhone X printable templates

Other: MediaPlayer framework secrets

Other: Extensive guide to WKWebView

Other: Animation with CABasicAnimation

Other: Adopting Dark Mode in iOS apps

Other: Supporting Dark Mode in iOS

Other: Advanced Property Wrappers

Other: From iOS to macOS development

Foundation

The Foundation framework provides a base layer of functionality for apps and frameworks, including data storage and persistence, text processing, date and time calculations, sorting and filtering, and networking [1].

Bundle

paths

UIKit

The UIKit framework provides the required infrastructure for your iOS or tvOS apps. It provides the window and view architecture for implementing your interface, the event handling infrastructure for delivering Multi-Touch and other types of input to your app, and the main run loop needed to manage interactions among the user, the system, and your app. Other features offered by the framework include animation support, document support, drawing and printing support, information about the current device, text management and display, search support, accessibility support, app extension support, and resource management [1].

UIPageViewController

The UIPageViewController class is a container view for presenting multiple view controllers as a series of pages that can be cycled through.

import UIKit

class PageViewController: UIPageViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Used when adding UIPageViewControllerDataSource
        // dataSource = self
    }
    
    // Class definition
}

orderedViewControllers

The orderedViewControllers: [UIViewController] variable contains a generated list of the view controllers to be used in a UIPageViewController. The view controllers are displayed in the order in which they appear in the list returned by the orderedViewControllers variable.

private(set) var orderedViewControllers: [UIViewController] = {
    func newViewController(_ name: String) -> UIViewController {
      	return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "page")
    }
    let viewOrder = ["page1", "page2", "page3"]
    let controllers = viewOrder.map { newViewController($0) }
    return controllers
}()

The above example uses a scoped function newViewController(_:) -> UIViewController to instantiate a new view controller using the storyboard identifier page. viewOrder.map { newViewController($0) } assigns a new list of view controllers to the controllers variable using the strings in viewOrder. Notice, however, that these strings are never passed to the newly instantiated view controllers. The below example shows how to send the data to a view controller, though this is not a necessary requirement of the UIPageViewController.

// Assume pages use a view controller similar to this
class ViewController: UIViewController {
    var title: String = "default value"

    // rest of view controller declaration
}

class PageViewController: UIPageViewController {
    private(set) var orderedViewControllers: [UIViewController] = {
        func newViewController(_ name: String) -> UIViewController {
            return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "page")
        }
        let viewOrder = ["page1", "page2", "page3"]
        let controllers = viewOrder.map { newViewController($0) as! ViewController }
        for i in 0 ..< controllers.count {
            controllers[i].page = viewOrder[i]
        }
        return controllers
    }()

    // rest of page view controller declaration
}

setViewControllers

The setViewControllers(_:, direction:, animated:, completion:) function is used to set the view controller to present to the user.

This can be used to set the initial view controller to present when the controller first loads.

class PageViewController: UIPageViewController {
	  override func viewDidLoad() {
        super.viewDidLoad()
  
        if let firstViewController = orderedViewControllers.first {
            setViewControllers(
                [firstViewController],
                direction: .forward,
                animated: true,
                completion: nil
            )
        }
    }
}

Customising the page dots

Customisation of the page controller dots can be achieved by modifying UIPageControl.appearance().

let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.red.withAlphaComponent(0.6)
appearance.currentPageIndicatorTintColor = .red
appearance.backgroundColor = .yellow

Reference

UIPageViewControllerDataSource

The UIPageViewControllerDataSource protocol is used to determine which view controller to present to the user. It can be implemented using:

class PageViewController: UIPageViewController, UIPageViewControllerDataSource {
    // Class definition
  
    // Extension function implementations
}

or separated for clarity using extensions:

class PageViewController: UIPageViewController {
    // Class definition
}

extension PageViewController: UIPageViewControllerDataSource {
    // Extension function implementations
}

viewControllerBefore

The pageViewController(pageViewController:, viewControllerBeforeViewController:) function is used to specify the view controller to present when moving backwards through the pages.

func pageViewController(
  pageViewController: UIPageViewController, 
  viewControllerBeforeViewController viewController: UIViewController
) -> UIViewController? {
    guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
      	return nil
    }

    let previousIndex = viewControllerIndex - 1

    guard previousIndex >= 0 else {
      	return nil
    }

    guard orderedViewControllers.count > previousIndex else {
      	return nil
    }

    return orderedViewControllers[previousIndex]
}

To loop back to the last view controller when moving backwards from the first view controller in the sequence, use the modified implementation below.

func pageViewController(
  pageViewController: UIPageViewController, 
  viewControllerBeforeViewController viewController: UIViewController
) -> UIViewController? {
    guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
      	return nil
    }

    let previousIndex = viewControllerIndex - 1

    // User is on the first view controller and swiped left to loop to the last view controller.
    guard previousIndex >= 0 else {
      	return orderedViewControllers.last
    }

    guard orderedViewControllers.count > previousIndex else {
      	return nil
    }

    return orderedViewControllers[previousIndex]
}

viewControllerAfter

The pageViewController(pageViewController:, viewControllerAfterViewController:) function is used to specify the view controller to present when moving forwards through the pages.

func pageViewController(
  pageViewController: UIPageViewController, 
  viewControllerAfterViewController viewController: UIViewController
) -> UIViewController? {
  guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
    	return nil
  }

  let nextIndex = viewControllerIndex + 1
  let orderedViewControllersCount = orderedViewControllers.count

  // User is on the last view controller and swiped right to loop to the first view controller.
  guard orderedViewControllersCount != nextIndex else {
    	return orderedViewControllers.first
  }

  guard orderedViewControllersCount > nextIndex else {
    	return nil
  }

  return orderedViewControllers[nextIndex]
}

To loop back to the first view controller when moving forwards from the last view controller in the sequence, use the modified implementation below.

func pageViewController(
  _ pageViewController: UIPageViewController, 
  viewControllerAfter viewController: UIViewController
) -> UIViewController? {
  guard let viewControllerIndex = orderedViewControllers.firstIndex(of: viewController) else {
    	return nil
  }

  let nextIndex = viewControllerIndex + 1
  let orderedViewControllersCount = orderedViewControllers.count

  // User is on the last view controller and swiped right to loop to the first view controller.
  guard orderedViewControllersCount != nextIndex else {
    	return orderedViewControllers.first
  }

  guard orderedViewControllersCount > nextIndex else {
    	return nil
  }

  return orderedViewControllers[nextIndex]
}

presentationCount

To show page dots in a UIPageViewController the presentationCount(for:) -> Int function informs the controller data source as to how many pages there are, and how many dots should be shown.

extension PageViewController: UIPageViewControllerDataSource {
  	func presentationCount(for pvc: UIPageViewController) -> Int {
        return orderedViewControllers.count
    }
}

presentationIndex

To show page dots in a UIPageViewController the presentationIndex(for:) -> Int function informs the controller data source as to which page is currently presented.

extension PageViewController: UIPageViewControllerDataSource {
	func presentationIndex(for pvc: UIPageViewController) -> Int {
  	guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = orderedViewControllers.firstIndex(of: firstViewController) else {
            return 0
        }
        
        return firstViewControllerIndex
    }
}

UITableView

didSelectRowAt

UITableViewDataSource

numberOfRowsInSelection

cellForRowAt

AppKit

AppKit contains all the objects you need to implement the user interface for a macOS app—windows, panels, buttons, menus, scrollers, and text fields—and it handles all the details for you as it efficiently draws on the screen, communicates with hardware devices and screen buffers, clears areas of the screen before drawing, and clips views.

The framework also provides APIs you use to make your app accessible to users with disabilities (see Accessibility); to learn more about localizing your app for different languages, countries, or cultural regions, see Internationalization and Localization Guide [1].

NSView

C/C++

Useful Pages

SO: Accessing files in macOS bundle

SO: Get home directory in Linux

SO: Issue with using execvp

Kotlin

Kotlin is a modern language developed by JetBrains.

Useful Pages

DidSet and willSet in Kotlin

Unreal Engine 4

Useful Pages

Other: Create nice and feasible volumetric cloud in Unreal Engine 4

Web Technologies

HTML

CSS

JavaScript

Windows 32 API

CreateProcessA

CreateProcess used to be the fundamental way to create a new process, used internally by higher level functions such as ShellExecute.

With the advent of User Access Control security, UAC, one needs to use ShellExecute (or family member) to run a process with elevated access.

Where the process to be started is a console subsystem process, it can often be more convenient to use the C++ standard library's system function, which uses the cmd.exe command interpreter to run a specified command, waits for that to end, and returns the process exit code.

As stated in MSDN, CreateProcessA's 2nd argument : lpApplicationName is playing the role of the command line to be executed. Unless lpApplicationName points to an exe in a directory, the system will look for it in the following order:

  1. The directory from which the application loaded.
  2. The current directory for the parent process.
  3. The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory
  4. The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  5. The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.

Therefore, unless the second argument of CreateProcessA is in the form {directory}/{executable_name}.{ext}, you'll have to either :

  1. Place executable_name in the same directory from which the application loaded
  2. Place executable_name in the same directory of the parent process
  3. Place executable_name in the Windows System32 directory : C:\Windows\System32
  4. Place executable_name in the Windows directory : C:\Windows
  5. Include the directory where executable_name is in the PATH

The second parameter in CreateProcess should be a writable buffer. If your executable path does not include command line arguments then put the executable path in the first parameter, and leave the second parameter NULL

GetExitCodeProcess

GetExitCodeProcess is used to check the exit code of a process. By convention in Windows, Unix and in C++, exit code 0 (e.g. as returned from main) denotes success. In C++ this value is available as the macro symbol EXIT_SUCCESS from <stdlib.h>. On other systems EXIT_SUCCESS needs not be 0, but in C++ exit code 0 always denotes success, possibly in addition to the value of EXIT_SUCCESS.

The Windows and Unix-land convention is that any other exit code denotes failure, but the only portable failure value in C++ is the one denoted by EXIT_FAILURE. In Windows this is conventionally the value 1. Unfortunately that conflicts with the value of a Windows error code, about “incorrect function” (as reported by errlook.exe). For this reason one may choose to use Windows' own general failure value E_FAIL to indicate a general failure, instead of using the portable but imperfect EXIT_FAILURE.

Two relevant differences between Windows and Unix-land:

  • In Windows a non-zero exit code is often an error code that tells you something about the cause of failure. It can be passed to a tool such as Microsoft's errlook to get a textual description, the same as via the API function FormatMessage. In Unix-land process exit codes used as failure cause indications are much less common.
  • In Unix-land tools can generally be relied on to indicate failure or success via the exit code. This is not so in Windows. Even many of the built-in and standard commands fail to do so.

Especially the latter point is important for use of GetExitCodeProcess. You need to know that the process you're checking, is one that produces a meaningful exit-code.

Also note well that the code STILL_ACTIVE is defined as a low number, as I recall around 270 or thereabouts, and can be produced by a process, so that it is not a reliable indicator of whether a process is still active or not. I.e., don't use GetExitCodeProcess to poll for a process to finish. Depending on the process & circumstances, such code might hang.

WaitForSingleObject

Instead of waiting for a process to end by polling its exit code with GetExitCodeProcess, which might hang as explained above, use e.g. WaitForSingleObject, or a family member.

WaitForSingleObject(pi.hProcess, INFINITE)
val exitCode: DWORDVar = alloc()
val result = GetExitCodeProcess(pi.hProcess, exitCode.ptr).convert<Int>()
CloseHandle(pi.hProcess)
CloseHandle(pi.hThread)

return if (result == 0) { // If result is 0, GetExitCodeProcess failed
    -1
} else {
    exitCode.value.convert()
}

POSIX

POSIX is a set of standards for providing compatibility between operating systems.

Useful Pages

SO: Fork and waitpid in C

SO: Example of waitpid in use

SO: Exit, fork, and waitpid

SO: Issue with using execvp

system

The int system(char* command) function executes the specified shell command in a new process and waits for the process to complete. The return value is the exit code of the executed command. As such, it cannot be assumed that a value of 0 means success as this is dependent on the command. system() is implemented using fork() and execl().

int system("ls");

If command is null then a return value of 0 indicates a shell is available on the system. A non-zero value indicates there is no available shell.

If a child process cannot be created, system() returns -1 and the global errno value is set.

fork

The int fork() function creates a new process as a duplicate of the current process. The return value indicates the status of the child process creation.

val pid: Int = fork()
when {
    pid == 0 -> {    // If result is 0, fork succeeded and we are the child
        // Code run only by child
    }
    pid < 0  -> {    // If result < 0, fork failed and we are the parent
        // Code run only by parent
    }
    else -> {        // If result > 0, fork succeeded and we are the parent
        // Code run only by parent
    }
}
// Code run by:
// - Parent
// - Child if did not return or exit already

exec

The int exec() family of functions replace the current process with a new one.

Variants

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
FunctionDescription
execl()execute file at specified path with arguments given as a varargs list
execlp()execute a file on the PATH with arguments given as a varargs list
execle()execute file at specified path with arguments given as a varargs list and environment parameters given as a vector list
execv()execute file at specified path with arguments given as a vector list
execvp()execute file on the PATH with arguments given as a vector list
execvpe()execute file on the PATH with arguments given as a vector list and environment parameters given as a vector list

Failure

If an exec variant fails to execute successfully, the currently executing process will not be replaced, and the global errno constant will be updated with an error value that indicates the cause of the problem. The full list of errors can be viewed here.

execl

The int execl(const char *path, const char *arg, ...) function is used to replace the current process by executing a file at the specified path with arguments given as a varargs list.

char* command = "/bin/ls";  // The file to execute

char* p1 = "ls";            // The first argument must be the file to execute
char* p2 = ".";             // Arguments to the command
char* p3 = "-al";           // Arguments to the command
char* p4 = NULL;            // The last argument must be the null terminating character

int result = execl(command, p1, p2, p3, p4); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

execlp

The int execlp(const char *file, const char *arg, ...) function is used to replace the current process by executing a file at the specified path with arguments given as a varargs list.

char* command = "ls";       // The file to execute

char* p1 = "ls";            // The first argument must be the file to execute
char* p2 = ".";             // Arguments to the command
char* p3 = "-al";           // Arguments to the command
char* p4 = NULL;            // The last argument must be the null terminating character

int result = execlp(command, p1, p2, p3, p4); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

execle

The int execle(const char *path, const char *arg, ...) function is used to replace the current process by executing a file at the specified path with arguments given as a varargs list and environment parameters given as a vector list.

char* command = "/bin/ls"; // The file to execute

char* p1 = "ls";           // The first argument must be the file to execute
char* p2 = ".";            // Arguments to the command
char* p3 = "-al";          // Arguments to the command
char* p4 = NULL;           // The last argument must be the null terminating character
char* env[] = {};          // The list of environmental parameters

int result = execle(command, p1, p2, p3, p4, environ); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

execv

The int execv(const char *file, char *const argv[]) function is used to replace the current process by executing a file at the specified path with arguments given as a vector list.

char* command = "/bin/ls";  // The file to execute

char* argv[4];              // The list of arguments to pass (size >= 2)
argv[0] = "ls";             // The first argument must be the file to execute
argv[1] = ".";              // Arguments to the command
argv[2] = "-al";            // Arguments to the command
argv[3] = NULL;             // The last argument must be the null terminating character

int result = execv(command, argv); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

execvp

The int execvp(const char *path, char *const argv[]) function is used to replace the current process by executing a file on the PATH with arguments given as a vector list.

char* command = "ls";  // The file on the path to execute

char* argv[4];         // The list of arguments to pass (size >= 2)
argv[0] = "ls";        // The first argument must be the file to execute
argv[1] = ".";         // Arguments to the command
argv[2] = "-al";       // Arguments to the command
argv[3] = NULL;        // The last argument must be the null terminating character

int result = execvp(command, argv); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

execvpe

The int execvpe(const char *path, char *const argv[]) function is used to replace the current process by executing a file on the PATH with arguments given as a vector list and environment parameters given as a vector list.

char* command = "ls";  // The file on the path to execute

char* argv[4];         // The list of arguments to pass (size >= 2)
argv[0] = "ls";        // The first argument must be the file to execute
argv[1] = ".";         // Arguments to the command
argv[2] = "-al";       // Arguments to the command
argv[3] = NULL;        // The last argument must be the null terminating character
char* env[] = {};      // The list of environmental parameters

int result = execvp(command, argv, environ); // Replace the current process
// If result is -1, exec failed
// Code beyond this point will not execute unless exec failed
// If exec failed, errno can be checked to determine the cause

errno

The global errno constant is set when certain function calls fail. Below is a list of errno values with associated descriptions.

Enum ValueValueDescription
EPERM1Operation not permitted
ENOENT2No such file or directory
ESRCH3No such process
EINTR4Interrupted system call
EIO5I/O error
ENXIO6No such device or address
E2BIG7Arg list too long
ENOEXEC8Exec format error
EBADF9Bad file number
ECHILD10No child processes
EAGAIN11Try again
ENOMEM12Out of memory
EACCES13Permission denied
EFAULT14Bad address
ENOTBLK15Block device required
EBUSY16Device or resource busy
EEXIST17File exists
EXDEV18Cross-device link
ENODEV19No such device
ENOTDIR20Not a directory
EISDIR21Is a directory
EINVAL22Invalid argument
ENFILE23File table overflow
EMFILE24Too many open files
ENOTTY25Not a typewriter
ETXTBSY26Text file busy
EFBIG27File too large
ENOSPC28No space left on device
ESPIPE29Illegal seek
EROFS30Read-only file system
EMLINK31Too many links
EPIPE32Broken pipe
EDOM33Math argument out of domain of func
ERANGE34Math result not representable
EDEADLK35Resource deadlock would occur
ENAMETOOLONG36File name too long
ENOLCK37No record locks available
ENOSYS38Function not implemented
ENOTEMPTY39Directory not empty
ELOOP40Too many symbolic links encountered
ENOMSG42No message of desired type
EIDRM43Identifier removed
ECHRNG44Channel number out of range
EL2NSYNC45Level 2 not synchronized
EL3HLT46Level 3 halted
EL3RST47Level 3 reset
ELNRNG48Link number out of range
EUNATCH49Protocol driver not attached
ENOCSI50No CSI structure available
EL2HLT51Level 2 halted
EBADE52Invalid exchange
EBADR53Invalid request descriptor
EXFULL54Exchange full
ENOANO55No anode
EBADRQC56Invalid request code
EBADSLT57Invalid slot
EBFONT59Bad font file format
ENOSTR60Device not a stream
ENODATA61No data available
ETIME62Timer expired
ENOSR63Out of streams resources
ENONET64Machine is not on the network
ENOPKG65Package not installed
EREMOTE66Object is remote
ENOLINK67Link has been severed
EADV68Advertise error
ESRMNT69Srmount error
ECOMM70Communication error on send
EPROTO71Protocol error
EMULTIHOP72Multihop attempted
EDOTDOT73RFS specific error
EBADMSG74Not a data message
EOVERFLOW75Value too large for defined data type
ENOTUNIQ76Name not unique on network
EBADFD77File descriptor in bad state
EREMCHG78Remote address changed
ELIBACC79Can not access a needed shared library
ELIBBAD80Accessing a corrupted shared library
ELIBSCN81.lib section in a.out corrupted
ELIBMAX82Attempting to link in too many shared libraries
ELIBEXEC83Cannot exec a shared library directly
EILSEQ84Illegal byte sequence
ERESTART85Interrupted system call should be restarted
ESTRPIPE86Streams pipe error
EUSERS87Too many users
ENOTSOCK88Socket operation on non-socket
EDESTADDRREQ89Destination address required
EMSGSIZE90Message too long
EPROTOTYPE91Protocol wrong type for socket
ENOPROTOOPT92Protocol not available
EPROTONOSUPPORT93Protocol not supported
ESOCKTNOSUPPORT94Socket type not supported
EOPNOTSUPP95Operation not supported on transport endpoint
ENOTSUP95Operation not supported (POSIX.1-2001).
EPFNOSUPPORT96Protocol family not supported
EAFNOSUPPORT97Address family not supported by protocol
EADDRINUSE98Address already in use
EADDRNOTAVAIL99Cannot assign requested address
ENETDOWN100Network is down
ENETUNREACH101Network is unreachable
ENETRESET102Network dropped connection because of reset
ECONNABORTED103Software caused connection abort
ECONNRESET104Connection reset by peer
ENOBUFS105No buffer space available
EISCONN106Transport endpoint is already connected
ENOTCONN107Transport endpoint is not connected
ESHUTDOWN108Cannot send after transport endpoint shutdown
ETOOMANYREFS109Too many references: cannot splice
ETIMEDOUT110Connection timed out
ECONNREFUSED111Connection refused
EHOSTDOWN112Host is down
EHOSTUNREACH113No route to host
EALREADY114Operation already in progress
EINPROGRESS115Operation now in progress
ESTALE116Stale NFS file handle
EUCLEAN117Structure needs cleaning
ENOTNAM118Not a XENIX named type file
ENAVAIL119No XENIX semaphores available
EISNAM120Is a named type file
EREMOTEIO121Remote I/O error
EDQUOT122Quota exceeded
ENOMEDIUM123No medium found
EMEDIUMTYPE124Wrong medium type
ECANCELED125Operation canceled (POSIX.1-2001).
ENOKEY126Required key not available.
EKEYEXPIRED127Key has expired.
EKEYREVOKED128Key has been revoked.
EKEYREJECTED129Key was rejected by service.
EOWNERDEAD130Owner died (POSIX.1-2008).
ENOTRECOVERABLE131State not recoverable (POSIX.1-2008).
ERFKILL132Operation not possible due to RF-kill.
EHWPOISON133Memory page has hardware error.
EWOULDBLOCKEAGAINOperation would block
EDEADLOCKEDEADLK

Tools

Xcode

Useful Pages

SO: Building a macOS bundle manually

SO: Adding music to Xcode simulators

Other: Submitting to the app store, Part 2

Swift Package Manager

Gradle

make/cmake