iOS Development
Introduction to iOS Development
iOS development involves creating applications for Apple's iOS platform using Swift or Objective-C. This guide focuses on modern iOS development using Swift and the latest tools and frameworks.
Development Requirements:
- Mac computer with macOS
- Xcode IDE
- Apple Developer Account
- iOS SDK
- Swift programming language
- iOS Simulator
Swift Programming Basics
Swift Language Features
// Variables and Constants
let constant = "This cannot be changed"
var variable = "This can be changed"
// Type Inference and Annotation
let inferredInt = 42
let explicitDouble: Double = 42.0
// Optionals
var optionalString: String?
var unwrappedString = optionalString ?? "default value"
// Guard Statement
func processAge(_ age: Int?) {
guard let validAge = age, validAge >= 18 else {
print("Invalid age")
return
}
print("Processing age: \(validAge)")
}
// Structures
struct User {
let id: String
var name: String
var email: String
mutating func updateEmail(_ newEmail: String) {
email = newEmail
}
}
// Classes
class Account {
let id: String
var balance: Double
init(id: String, balance: Double) {
self.id = id
self.balance = balance
}
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws {
guard balance >= amount else {
throw NSError(
domain: "AccountError",
code: 1,
userInfo: [
NSLocalizedDescriptionKey:
"Insufficient funds"
]
)
}
balance -= amount
}
}
// Protocols
protocol DataProvider {
func fetchData() async throws -> Data
}
// Extensions
extension String {
var isValidEmail: Bool {
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let predicate = NSPredicate(format: "SELF MATCHES %@", pattern)
return predicate.evaluate(with: self)
}
}
// Enums with Associated Values
enum NetworkError: Error {
case invalidURL
case noData
case serverError(Int)
case decodingError(Error)
}
UI Development with SwiftUI
SwiftUI View Example
import SwiftUI
struct ContentView: View {
@State private var username = ""
@State private var password = ""
@State private var isLoading = false
@State private var showAlert = false
@State private var alertMessage = ""
var body: some View {
NavigationView {
Form {
Section(header: Text("Login")) {
TextField("Username", text: $username)
.autocapitalization(.none)
SecureField("Password", text: $password)
}
Section {
Button(action: login) {
if isLoading {
ProgressView()
} else {
Text("Sign In")
.frame(maxWidth: .infinity)
}
}
.disabled(username.isEmpty || password.isEmpty)
}
}
.navigationTitle("Welcome")
.alert("Error", isPresented: $showAlert) {
Button("OK", role: .cancel) {}
} message: {
Text(alertMessage)
}
}
}
private func login() {
isLoading = true
// Simulated network call
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
isLoading = false
if username == "test" && password == "password" {
// Success - Navigate to next screen
} else {
alertMessage = "Invalid credentials"
showAlert = true
}
}
}
}
// Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Custom Components
struct CustomButton: View {
let title: String
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
}
struct LoadingView: View {
let message: String
var body: some View {
VStack(spacing: 20) {
ProgressView()
.scaleEffect(1.5)
Text(message)
.font(.body)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(0.1))
}
App Architecture
MVVM Architecture Example
// Model
struct User: Codable {
let id: String
let name: String
let email: String
}
// ViewModel
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
@Published var error: Error?
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol) {
self.userService = userService
}
@MainActor
func fetchUser(id: String) async {
isLoading = true
error = nil
do {
user = try await userService.fetchUser(id: id)
} catch {
self.error = error
}
isLoading = false
}
}
// Service Protocol
protocol UserServiceProtocol {
func fetchUser(id: String) async throws -> User
}
// Service Implementation
class UserService: UserServiceProtocol {
func fetchUser(id: String) async throws -> User {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
throw URLError(.badURL)
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(User.self, from: data)
}
}
Networking
Network Layer Implementation
// Network Error Enum
enum NetworkError: LocalizedError {
case invalidURL
case requestFailed(Error)
case invalidResponse
case decodingError(Error)
var errorDescription: String? {
switch self {
case .invalidURL:
return "Invalid URL"
case .requestFailed(let error):
return error.localizedDescription
case .invalidResponse:
return "Invalid server response"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
}
}
}
// Network Service Protocol
protocol NetworkServiceProtocol {
func fetch(
_ endpoint: String,
method: String,
body: Data?
) async throws -> T
}
// Network Service Implementation
class NetworkService: NetworkServiceProtocol {
private let baseURL: String
private let session: URLSession
init(baseURL: String, session: URLSession = .shared) {
self.baseURL = baseURL
self.session = session
}
func fetch(
_ endpoint: String,
method: String = "GET",
body: Data? = nil
) async throws -> T {
guard let url = URL(string: baseURL + endpoint) else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = method
request.httpBody = body
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw NetworkError.decodingError(error)
}
} catch {
throw NetworkError.requestFailed(error)
}
}
}
Data Persistence
CoreData Implementation
// CoreData Manager
class CoreDataManager {
static let shared = CoreDataManager()
private let persistentContainer: NSPersistentContainer
private init() {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores { description, error in
if let error = error {
fatalError("Core Data store failed to load: \(error)")
}
}
}
var context: NSManagedObjectContext {
persistentContainer.viewContext
}
func saveContext() {
if context.hasChanges {
do {
try context.save()
} catch {
let error = error as NSError
fatalError("An error occurred: \(error)")
}
}
}
}
// Repository Pattern
protocol UserRepository {
func save(_ user: User) throws
func fetchUser(id: String) throws -> User?
func fetchAllUsers() throws -> [User]
func delete(_ user: User) throws
}
class CoreDataUserRepository: UserRepository {
private let context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
func save(_ user: User) throws {
let userEntity = UserEntity(context: context)
userEntity.id = user.id
userEntity.name = user.name
userEntity.email = user.email
try context.save()
}
func fetchUser(id: String) throws -> User? {
let request = UserEntity.fetchRequest()
request.predicate = NSPredicate(
format: "id == %@",
id
)
let result = try context.fetch(request)
return result.first.map { User(entity: $0) }
}
func fetchAllUsers() throws -> [User] {
let request = UserEntity.fetchRequest()
let results = try context.fetch(request)
return results.map { User(entity: $0) }
}
func delete(_ user: User) throws {
guard let userEntity = try fetchUserEntity(id: user.id) else {
return
}
context.delete(userEntity)
try context.save()
}
private func fetchUserEntity(id: String) throws -> UserEntity? {
let request = UserEntity.fetchRequest()
request.predicate = NSPredicate(
format: "id == %@",
id
)
return try context.fetch(request).first
}
}
App Deployment
Deployment Steps:
- Configure App Settings:
- Bundle identifier
- Version number
- Build number
- Deployment target
- Code Signing:
- Certificates
- Provisioning profiles
- Development team
- App Store Connect:
- App information
- Screenshots
- Description
- Keywords
- Testing:
- TestFlight
- Beta testing
- Review guidelines