š Hi there, Iām Steven
Iām an iOS Software Engineer.
Coding
Designing
Playing Table Tennis and Guitar
Learning new about IT
Xcode
SourceTree
Postman
Figma
Sketch
AdobeXD
Jira
Confluence
GitHub
Bitbucket
š§° Languages and Frameworks that I work with:
Swift
Clean Swift/MVVM
UIKit
AVFoundation
CoreLocation
Alamofire/Moya
Realm/CoreData
Apple Maps/Google Maps
Firebase
Google Places
Facebook SDK
OneSignal
Security
LocalAuthentication
StoreKit
Setting up App's global behavior
class ApplicationGeneralSetupManager {
static var shared = ApplicationGeneralSetupManager ( )
func performGeneralSetup( _ launchOptions: [ UIApplication . LaunchOptionsKey : Any ] ? ) {
setupAppDependencies ( launchOptions)
performRealmSetupAndMigration ( )
setupGlobalAppearance ( )
performOtherConfiguration ( )
}
//MARK: - Dependencies setup
private func setupAppDependencies( _ launchOptions: [ UIApplication . LaunchOptionsKey : Any ] ? ) {
/// Some setup code here
}
// MARK: - Other setup
private func performOtherConfiguration( ) {
/// Some setup code here
}
// MARK: - Realm setup
private func performRealmSetupAndMigration( ) {
/// Some setup code here
}
// MARK: - Global appearance setup
private func setupGlobalAppearance( ) {
/// Some setup code here
}
}
// MARK: - Object lifecycle
override init ( nibName nibNameOrNil: String ? , bundle nibBundleOrNil: Bundle ? ) {
super. init ( nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup ( )
}
required init ? ( coder aDecoder: NSCoder ) {
super. init ( coder: aDecoder)
setup ( )
}
// MARK: - View lifecycle
override func viewDidLoad( ) {
super. viewDidLoad ( )
setupProductsCollectionView ( )
interactor? . prepareCollectionViewData ( )
}
override func viewWillAppear( _ animated: Bool ) {
setupNotificationsButton ( )
}
// MARK: - Actions
@IBAction func didPressNotifications( _ sender: UIBarButtonItem ) {
performSegue ( withIdentifier: " NotificationsFromMenu " , sender: self )
}
@objc
private func didPullToRefresh( _ sender: Any ) {
DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.25) {
self . interactor? . getAllProducts ( )
}
}
func showPopupErrorMessage( title: String , body: String ) {
refreshControl. endRefreshing ( )
showTopErrorView ( title: title, message: body)
}
private func selectFirstFiltersCell( ) {
let firstCellIndex = IndexPath ( item: 0 , section: 0 )
filtersCollectionView. selectItem ( at: firstCellIndex, animated: false, scrollPosition: . centeredHorizontally)
}
// MARK: - UI Setup
private func setupProductsCollectionView( ) {
let columnLayout = ColumnFlowLayout ( cellsPerRow: 2 ,
minimumInterItemSpacing: 10 ,
minimumLineSpacing: 10 ,
sectionInset: UIEdgeInsets ( top: 0 , left: 12 , bottom: 20 , right: 12 )
)
refreshControl. addTarget ( self , action: #selector( didPullToRefresh ( _: ) ) , for: . valueChanged)
productsCollectionView. alwaysBounceVertical = true
productsCollectionView. refreshControl = refreshControl
productsCollectionView. dataSource = self
productsCollectionView. delegate = self
productsCollectionView. collectionViewLayout = columnLayout
productsCollectionView. contentInsetAdjustmentBehavior = . always
productsCollectionView. register ( UINib ( nibName: " ProductCollectionViewCell " , bundle: nil ) ,
forCellWithReuseIdentifier: " ProductCollectionViewCell " )
}
private func setupNotificationsButton( ) {
if GlobalData . enteredAsAGuest {
notificationsBarButton. image = UIImage ( )
return
}
if GlobalData . hasUnreadNotifications {
notificationsBarButton. image = UIImage ( named: " NotificationsFilledIcon " )
} else {
notificationsBarButton. image = UIImage ( named: " NotificationsIcon " )
}
}
// MARK: - Displaying Data
func displayProducts( _ data: [ ProductModel ] ) {
refreshControl. endRefreshing ( )
products = data
productsCollectionView. reloadData ( )
}
func updateProducts( ) {
interactor? . updateProductList ( )
}
// MARK: - Routing
override func prepare( for segue: UIStoryboardSegue , sender: Any ? ) {
if let scene = segue. identifier {
let selector = NSSelectorFromString ( " routeTo \( scene) WithSegue: " )
if let router = router, router. responds ( to: selector) {
router. perform ( selector, with: segue)
}
}
}
func performSignUpRequest( with data: SignUp . RequestModel ) {
print ( " Sign up request, \n POST - \( RequestLinks . signUp) " )
AF . request ( RequestLinks . signUp, method: . post, parameters: data, encoder: JSONParameterEncoder . default, headers: nil )
. validate ( )
. responseDecodable ( of: SignUp . RegistrationResponse. self) { response in
switch response. result {
case . success( let responseData) :
self . presenter? . openPhoneVerificationScene ( )
case . failure( let error) :
let errorMessage = APIErrorsHandler . handleErrorResponse ( response: response. response,
responseData: response. data,
error: error)
self . presenter? . showPopupErrorMessage ( title: " Error " ,
body: errorMessage)
}
}
}
func getUserData( completion: @escaping ( Result < UserModel , APIErrors > ) -> Void ) {
let headers = APIManagerSetup . defaultHeader
var requestLink = RequestLinks . userData
if TokenManager . getUserId ( ) . isEmpty {
completion ( . error( . invalidUserId) )
return
}
requestLink = requestLink + " ?userId= \( TokenManager . getUserId ( ) ) "
print ( " Get user data request, \n GET - " , requestLink)
AF . request ( requestLink, method: . get, parameters: nil , encoding: JSONEncoding . default, headers: headers)
. validate ( )
. responseDecodable ( of: UserModel . self) { response in
switch response. result {
case . success( let responseData) :
TokenManager . save ( token: responseData. accessToken, userId: responseData. userId)
completion ( . success( responseData) )
case . failure( let error) :
print ( " Alamofire error - " , error)
completion ( . error( . unableToComplete) )
}
}
}
func upload( photos: [ UIImage ] , recognizedImageIds: [ String ] , objectId: Int , objectType: Int , operationType: ItemDetails . ItemLibraryActions ) {
let endUrl = RequestLinks . uploadPhoto + " / \( objectType) / \( objectId) "
let headers = APIManagerSetup . defaultHeader
AF . upload ( multipartFormData: { multipartFormData in
for (index, photo) in photos. enumerated ( ) {
let imageData = photo. jpegData ( compressionQuality: 0.25)
for id in recognizedImageIds {
if let idData = id. data ( using: . utf8) {
multipartFormData. append ( idData, withName: " recognized_image_ids[] " )
}
}
multipartFormData. append ( imageData!, withName: " photos[ \( index) ] " , fileName: " Image- \( index) .png " , mimeType: " image/png " )
}
} ,
to: endUrl, method: . post , headers: headers)
. uploadProgress ( queue: . main, closure: { progress in
print ( " Upload Progress: \( progress. fractionCompleted) " )
} )
. response { response in
switch response. result {
case . success( _) :
print ( " Uploaded photo successfully. " )
self . showPopupMessageBasedOn ( operationType: operationType,
errorsWithPhotos: false)
case . failure( let error) :
print ( " Got error when uploading image. " , error. localizedDescription)
self . showPopupMessageBasedOn ( operationType: operationType,
errorsWithPhotos: true)
}
}
}
Processing and storing data
func updateOrderFromOrderDetails( ) {
guard let selectedOrder = selectedOrder else { return }
guard let currentDisplayingType = currentDisplayingType else { return }
if let orderToModify = allOrders. enumerated ( ) . first ( where: { $0. element. id == selectedOrder. id} ) {
allOrders [ orderToModify. offset] = selectedOrder
}
self . currentOrders = allOrders. filter { $0. status != . cancelled && $0. status != . completed }
self . pastOrders = allOrders. filter { $0. status != . preparing && $0. status != . ready }
prepareAndDisplayOrders ( of: currentDisplayingType)
}
func loadImageFromDocumentsDirectory( fileName: String ) -> UIImage {
let placeHolderImage = UIImage ( named: " PlacesPlaceholder " ) !
let documentsURL = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) [ 0 ]
let filePath = documentsURL. appendingPathComponent ( fileName)
let image = UIImage ( contentsOfFile: filePath. path)
if let savedImage = image {
return savedImage
} else {
return placeHolderImage
}
}
private func removeSavedFile( fileName: String ) {
let documentsURL = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) [ 0 ]
let filePath = documentsURL. appendingPathComponent ( fileName)
do {
if fileName. count > 0 {
try FileManager . default. removeItem ( at: filePath)
}
} catch let error as NSError {
print ( " Error: \( error. domain) " , error)
}
}
š This is the list of my projects:
Barcode & image scanner software that helps companies to keep track of their products
- Barcode scanner;
- Custom camera picker with image recognition;
- Offline mode;
- Multiple customizable roles;
- Customizable item and list cards;
- In-App purchases;
- Swift
- Clean Swift architecture
- UIKit
- AVFoundation
- CoreLocation
- Alamofire
- Realm
- Firebase Analytics
- StoreKit
Travel guide app that allows users to explore different European cities
- Explore and listen to audio guides of multiple places;
- Get dirrections to selected places;
- Walk curated routes or build your own;
- Download city data for offline usage;
- Save your current accommodation in selected city;
- In-App purchases;
- Swift
- Clean Swift architecture
- UIKit
- AVFoundation
- CoreLocation
- Google Maps
- Alamofire
- Realm
- Firebase
- StoreKit
- Social Media Sign up & Log In
Daily self care habit tracker
- Habit tracker;
- Events calendar;
- Important people organizer;
- Daily challenges;
- Social media sharing;
- Customizable weekly task schedule;
- Swift
- MVC architecture
- UIKit
- AVFoundation
- Moya
- Realm
- Google Maps
- Keychain
- SnapKit
- NaturalLanguage
- Firebase Analytics
- Facebook Analytics
- Social Media Sign up & Log In
Fitness tracker app where you get rewards for being active
- Track steps, floors and distance covered every day;
- Complete everyday challenges based on your activity;
- Receive progressive rewards;
- Purchase discounts by rewards earned for activity;
- Invite friends and get bonuses;
- Swift
- Viper architecture
- UIKit
- Alamofire
- Firebase
- Firebase Analytics
- Health Kit
Real Estate photograpgher & editor
- Create your properties by taking pictures of them;
- Get access to professional photo editors;
- Receive professionaly eddited images;
- Swift
- MVVM architecture
- UIKit
- AVFoundation
- Moya
- Firebase Analytics
- Keychain
- Audio Toolbox