๐ 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