Pokemon-iOS, simple app using CleanSwift architecture

I wrote two simple android apps, using MVP:

Now, I wrote a similar app but this time for iOS, using concepts from CleanSwift (really nice blog post series).

https://github.com/matoelorriaga/pokemon-ios

 

Advertisements
Pokemon-iOS, simple app using CleanSwift architecture

Gradle: update dependencies using Android Studio

Today I discover an “experimental” feature in Android Studio. Since version 2.2, there is a new Project Structure dialog that allows update dependencies and get information about new releases.

Captura de pantalla 2017-04-04 a la(s) 02.49.45.png

I first assumed that the code editor in Android Studio follows something like Semantic Versioning in order to highlight minor update in dependencies, but not latest patches and beta (even alpha) versions, but that is not the case.

Android Studio will only highlight dependencies that Gradle consider as “promoted”. That means that “Gradle found some dependency conflict, and used default conflict resolution strategy, which is to prefer a newer version of some dependency” (thanks Stanislav @ StackOverflow for your answer here. More information about dependency resolution here.

To enable it go to Preferences > Build, Execution, Deployment > Gradle > Experimental and check Use new ‘Project Structure’ dialog.

Captura de pantalla 2017-04-04 a la(s) 02.49.30.png

Hope you find it useful!

Gradle: update dependencies using Android Studio

Kokémon: MVP + Kotlin + Unit/UI tests

Some time ago I wrote https://github.com/matoelorriaga/pokemon, now I wrote a very similar app, but using Kotlin: https://github.com/matoelorriaga/kokemon.

Features:

Tell me what you think! Thanks!

Kokémon: MVP + Kotlin + Unit/UI tests

Using SnapKit on a real app

In my previous post, I wrote about some libraries used to do AutoLayout in code. Finally, I decided to use SnapKit in a small app. Here is the code and the screens, hope you find this useful. (logos are blacked out). I’m only showing the parts of the code related to SnapKit, so do not expect to copy, paste, and run it 😛

HeaderViewController: all view controller inherits from this class, it contains the header part of the screen.

class HeaderViewController: UIViewController
{
    let header = UIView()
    let logo = UIImageView()
    let subLogos = UIImageView()
 
    func initHeader()
    {
        initHeaderView()
        initLogoImageView()
        initSubLogosImageView()
        setBackgroundColor()
    }
 
    private func initHeaderView()
    {
        view.addSubview(header)
        header.backgroundColor = UIColor(hex: "#575353")
        header.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.height.equalTo(view.snp.height).dividedBy(3)
        }
    }
 
    private func initLogoImageView()
    {
        header.addSubview(logo)
        logo.image = UIImage(named: "logo_it_point")
        logo.contentMode = .scaleAspectFit
        logo.snp.makeConstraints { make in
            make.topMargin.equalTo(30)
            make.centerX.equalToSuperview()
        }
    }
 
    private func initSubLogosImageView()
    {
        header.addSubview(subLogos)
        subLogos.image = UIImage(named: "logos_white")
        subLogos.contentMode = .scaleAspectFit
        subLogos.snp.makeConstraints { make in
            make.top.equalTo(logo.snp.bottom)
            make.left.equalToSuperview().offset(8)
            make.right.equalToSuperview().inset(8)
            make.bottom.equalToSuperview()
            make.height.equalToSuperview().dividedBy(5)
        }
    }
 
    private func setBackgroundColor()
    {
        view.backgroundColor = UIColor(hex: "#F5F5F5")
    }
}

Login screen

jahskdash

class LoginViewController: HeaderViewController
{
    let usernameLabel = UILabel()
    let usernameTextField = UITextField()
    let passwordLabel = UILabel()
    let passwordTextField = UITextField()
    let tokenLabel = UILabel()
    let tokenTextField = UITextField()
    let loginButton = UIButton()
 
    override func viewDidLoad()
    {
        super.viewDidLoad()
        initUI()
    }
 
    private func initUI()
    {
        initHeader()
        initUsernameLabel()
        initUsernameTextField()
        initPasswordLabel()
        initPasswordTextField()
        initTokenLabel()
        initTokenTextField()
        initLoginButton()
    }
 
    private func initUsernameLabel()
    {
        view.addSubview(usernameLabel)
        usernameLabel.text = "Usuario"
        usernameLabel.font = UIFont.systemFont(ofSize: 12)
        usernameLabel.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.topMargin.equalTo(header.snp.bottom).offset(36)
        }
    }

    private func initUsernameTextField()
    {
        view.addSubview(usernameTextField)
        usernameTextField.borderStyle = .roundedRect
        usernameTextField.autocapitalizationType = .none
        usernameTextField.autocorrectionType = .no
        usernameTextField.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.rightMargin.equalTo(-24)
            make.top.equalTo(usernameLabel.snp.bottom).offset(6)
        }
    }
 
    private func initPasswordLabel()
    {
        view.addSubview(passwordLabel)
        passwordLabel.text = "Clave"
        passwordLabel.font = UIFont.systemFont(ofSize: 12)
        passwordLabel.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.top.equalTo(usernameTextField.snp.bottom).offset(24)
        }
    }
 
    private func initPasswordTextField()
    {
        view.addSubview(passwordTextField)
        passwordTextField.borderStyle = .roundedRect
        passwordTextField.isSecureTextEntry = true
        passwordTextField.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.rightMargin.equalTo(-24)
            make.top.equalTo(passwordLabel.snp.bottom).offset(6)
        }
    }
 
    private func initTokenLabel()
    {
        view.addSubview(tokenLabel)
        tokenLabel.text = "Token"
        tokenLabel.font = UIFont.systemFont(ofSize: 12)
        tokenLabel.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.top.equalTo(passwordTextField.snp.bottom).offset(24)
        }
    }
 
    private func initTokenTextField()
    {
        view.addSubview(tokenTextField)
        tokenTextField.borderStyle = .roundedRect
        tokenTextField.autocapitalizationType = .none
        tokenTextField.autocorrectionType = .no
        tokenTextField.snp.makeConstraints { make in
            make.leftMargin.equalTo(24)
            make.rightMargin.equalTo(-24)
            make.top.equalTo(tokenLabel.snp.bottom).offset(6)
        }
    }
 
    private func initLoginButton()
    {
        view.addSubview(loginButton)
        loginButton.setTitle("Ingresar", for: .normal)
        loginButton.backgroundColor = UIColor(hex: "#4098C8")
        loginButton.addTarget(self, action: #selector(loginButtonClicked(sender:)), for: .touchUpInside)
        loginButton.snp.makeConstraints { make in
            make.top.equalTo(tokenTextField.snp.bottom).offset(36)
            make.width.equalToSuperview().dividedBy(3)
            make.centerX.equalToSuperview()
        }
    }
}

Tickets list screen

lkqjwelk

class TicketsListViewController: HeaderViewController
{
    let newTicketButton = UIButton()
    let ticketsHistoryLabel = UILabel()
    let tableView = UITableView()
 
    override func viewDidLoad()
    {
        super.viewDidLoad()
        initUI()
    }
 
    private func initUI()
    {
        initHeader()
        initNewTicketButton()
        initTicketsHistoryLabel()
        initTableView()
    }
 
    private func initNewTicketButton()
    {
        view.addSubview(newTicketButton)
        newTicketButton.setTitle("Nuevo Ticket", for: .normal)
        newTicketButton.backgroundColor = UIColor(hex: "#4098C8")
        newTicketButton.addTarget(self, action: #selector(newTicketButtonClicked(sender:)), for: .touchUpInside)
        newTicketButton.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(8)
            make.right.equalToSuperview().inset(8)
            make.top.equalTo(header.snp.bottom).offset(8)
        }
    }
 
    private func initTicketsHistoryLabel()
    {
        view.addSubview(ticketsHistoryLabel)
        ticketsHistoryLabel.text = "Historial de tickets"
        ticketsHistoryLabel.font = UIFont.systemFont(ofSize: 14)
        ticketsHistoryLabel.snp.makeConstraints { make in
            make.top.equalTo(newTicketButton.snp.bottom).offset(12)
            make.centerX.equalToSuperview()
        }
    }
 
    private func initTableView()
    {
        view.addSubview(tableView)
        tableView.backgroundColor = UIColor(hex: "#F5F5F5")
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.separatorColor = UIColor.clear
        tableView.snp.makeConstraints { make in
            make.top.equalTo(ticketsHistoryLabel.snp.bottom).offset(6)
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.bottom.equalToSuperview()
        }
    }
}

New ticket screen

kjalska

class NewTicketViewController: HeaderViewController
{
    let newTicketLabel = UILabel()
    let newTicketImageView = UIImageView()
    let ticketTypesLabel = UILabel()
    let ticketTypesTextField = UITextField()
    let ticketTypesPickerView = UIPickerView()
    let notesLabel = UILabel()
    let notesTextView = UITextView()
    let backButton = UIButton()
    let saveButton = UIButton()
 
    override func viewDidLoad()
    {
        super.viewDidLoad()
        initUI()
    }
 
    private func initUI()
    {
        initHeader()
        initNewTicketLabel()
        initNewTicketImageView()
        initTicketTypesLabel()
        initTicketTypesTextField()
        initNotesLabel()
        initNotesTextView()
        initBackButton()
        initSaveButton()
    }
 
    private func initNewTicketLabel()
    {
        view.addSubview(newTicketLabel)
        newTicketLabel.text = "Nuevo Ticket"
        newTicketLabel.font = UIFont.systemFont(ofSize: 14)
        newTicketLabel.snp.makeConstraints { make in
            make.top.equalTo(header.snp.bottom).offset(16)
            make.centerX.equalToSuperview().offset(12)
        }
    }
 
    private func initNewTicketImageView()
    {
        view.addSubview(newTicketImageView)
        newTicketImageView.image = UIImage(named: "mini_logo")
        newTicketImageView.contentMode = .scaleAspectFit
        newTicketImageView.snp.makeConstraints { make in
            make.height.equalTo(24)
            make.centerY.equalTo(newTicketLabel.snp.centerY)
            make.right.equalTo(newTicketLabel.snp.left)
        }
    }
 
    private func initTicketTypesLabel()
    {
        view.addSubview(ticketTypesLabel)
        ticketTypesLabel.text = "Tipo"
        ticketTypesLabel.font = UIFont.systemFont(ofSize: 12)
        ticketTypesLabel.snp.makeConstraints { make in
            make.leftMargin.equalTo(16)
            make.top.equalTo(newTicketLabel.snp.bottom).offset(12)
        }
    }
 
    private func initTicketTypesTextField()
    {
        view.addSubview(ticketTypesTextField)
        ticketTypesTextField.borderStyle = .roundedRect
        ticketTypesTextField.autocapitalizationType = .none
        ticketTypesTextField.autocorrectionType = .no
        ticketTypesTextField.inputView = ticketTypesPickerView
        ticketTypesTextField.setCustomDoneTarget(self, action: #selector(doneClickedForTicketTypesTextField(sender:)))
        ticketTypesTextField.snp.makeConstraints { make in
            make.leftMargin.equalTo(16)
            make.rightMargin.equalTo(-16)
            make.top.equalTo(ticketTypesLabel.snp.bottom).offset(6)
        }
    }
 
    private func initNotesLabel()
    {
        view.addSubview(notesLabel)
        notesLabel.text = "Observaciones"
        notesLabel.font = UIFont.systemFont(ofSize: 12)
        notesLabel.snp.makeConstraints { make in
            make.leftMargin.equalTo(16)
            make.top.equalTo(ticketTypesTextField.snp.bottom).offset(12)
        }
    }
 
    private func initNotesTextView()
    {
        view.addSubview(notesTextView)
        notesTextView.bordered()
        notesTextView.autocapitalizationType = .none
        notesTextView.autocorrectionType = .no
        notesTextView.snp.makeConstraints { make in
            make.leftMargin.equalTo(16)
            make.rightMargin.equalTo(-16)
            make.top.equalTo(notesLabel.snp.bottom).offset(6)
            make.height.equalTo(100)
        }
    }
 
    private func initBackButton()
    {
        view.addSubview(backButton)
        backButton.setTitle("Atras", for: .normal)
        backButton.backgroundColor = UIColor(hex: "#4098C8")
        backButton.addTarget(self, action: #selector(backButtonClicked(sender:)), for: .touchUpInside)
        backButton.snp.makeConstraints { make in
            make.top.equalTo(notesTextView.snp.bottom).offset(16)
            make.left.equalTo(notesTextView.snp.left)
        }
    }
 
    private func initSaveButton()
    {
        view.addSubview(saveButton)
        saveButton.setTitle("Guardar", for: .normal)
        saveButton.backgroundColor = UIColor(hex: "#4098C8")
        saveButton.addTarget(self, action: #selector(saveButtonClicked(sender:)), for: .touchUpInside)
        saveButton.snp.makeConstraints { make in
            make.top.equalTo(notesTextView.snp.bottom).offset(16)
            make.right.equalTo(notesTextView.snp.right)
            make.left.equalTo(backButton.snp.right).offset(6)
            make.width.equalTo(backButton.snp.width)
        }
    }
}

Ticket detail screen

jsdhfkjds

class TicketDetailViewController: HeaderViewController
{
    let ticketDetailLabel = UILabel()
    let ticketDetailImageView = UIImageView()
    let cardView = CardView()
    let dateTimeLabel = UILabel()
    let dateTimeValueLabel = UILabel()
    let ticketTypeDescriptionLabel = UILabel()
    let ticketTypeDescriptionValueLabel = UILabel()
    let notesLabel = UILabel()
    let notesValueLabel = UILabel()
    let backButton = UIButton()
    let newTicketButton = UIButton()

    override func viewDidLoad()
    {
        super.viewDidLoad()
        initUI()
    }
 
    private func initUI()
    {
        initHeader()
        initTicketDetailLabel()
        initTicketDetailImageView()
        initCardView()
        initDateTimeLabel()
        initDateTimeValueLabel()
        initTicketTypeDescriptionLabel()
        initTicketTypeDescriptionValueLabel()
        initNotesLabel()
        initNotesValueLabel()
        initBackButton()
        initNewTicketButton()
    }
 
    private func initTicketDetailLabel()
    {
        view.addSubview(ticketDetailLabel)
        ticketDetailLabel.text = "Detalle del Ticket"
        ticketDetailLabel.font = UIFont.systemFont(ofSize: 14)
        ticketDetailLabel.snp.makeConstraints { make in
            make.top.equalTo(header.snp.bottom).offset(16)
            make.centerX.equalToSuperview().offset(12)
        }
    }
 
    private func initTicketDetailImageView()
    {
        view.addSubview(ticketDetailImageView)
        ticketDetailImageView.image = UIImage(named: "mini_logo")
        ticketDetailImageView.contentMode = .scaleAspectFit
        ticketDetailImageView.snp.makeConstraints { make in
            make.height.equalTo(24)
            make.centerY.equalTo(ticketDetailLabel.snp.centerY)
            make.right.equalTo(ticketDetailLabel.snp.left)
        }
    }
 
    private func initCardView()
    {
        view.addSubview(cardView)
        cardView.backgroundColor = .white
        cardView.snp.makeConstraints { make in
            make.leftMargin.equalToSuperview().offset(16)
            make.rightMargin.equalToSuperview().inset(16)
            make.top.equalTo(ticketDetailLabel.snp.bottom).offset(18)
        }
    }
 
    private func initDateTimeLabel()
    {
        cardView.addSubview(dateTimeLabel)
        dateTimeLabel.text = "Fecha"
        dateTimeLabel.font = UIFont.systemFont(ofSize: 12)
        dateTimeLabel.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(8)
            make.top.equalToSuperview().offset(8)
        }
    }
 
    private func initDateTimeValueLabel()
    {
        cardView.addSubview(dateTimeValueLabel)
        dateTimeValueLabel.font = UIFont.systemFont(ofSize: 12)
        dateTimeValueLabel.textColor = .lightGray
        dateTimeValueLabel.snp.makeConstraints { make in
            make.left.equalTo(dateTimeLabel.snp.right).offset(4)
            make.top.equalToSuperview().offset(8)
        }
    }
 
    private func initTicketTypeDescriptionLabel()
    {
        cardView.addSubview(ticketTypeDescriptionLabel)
        ticketTypeDescriptionLabel.text = "Tipo"
        ticketTypeDescriptionLabel.font = UIFont.systemFont(ofSize: 12)
        ticketTypeDescriptionLabel.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(8)
            make.top.equalTo(dateTimeLabel.snp.bottom).offset(8)
        }
    }
 
    private func initTicketTypeDescriptionValueLabel()
    {
        cardView.addSubview(ticketTypeDescriptionValueLabel)
        ticketTypeDescriptionValueLabel.font = UIFont.systemFont(ofSize: 12)
        ticketTypeDescriptionValueLabel.textColor = .lightGray
        ticketTypeDescriptionValueLabel.snp.makeConstraints { make in
            make.left.equalTo(ticketTypeDescriptionLabel.snp.right).offset(4)
            make.top.equalTo(dateTimeValueLabel.snp.bottom).offset(8)
        }
    }
 
    private func initNotesLabel()
    {
        cardView.addSubview(notesLabel)
        notesLabel.text = "Observaciones"
        notesLabel.font = UIFont.systemFont(ofSize: 12)
        notesLabel.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(8)
            make.top.equalTo(ticketTypeDescriptionLabel.snp.bottom).offset(8)
        }
    }
 
    private func initNotesValueLabel()
    {
        cardView.addSubview(notesValueLabel)
        notesValueLabel.font = UIFont.systemFont(ofSize: 12)
        notesValueLabel.textColor = .lightGray
        notesValueLabel.numberOfLines = 0
        notesValueLabel.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(8)
            make.right.equalToSuperview().inset(8)
            make.top.equalTo(notesLabel.snp.bottom).offset(4)
            make.bottom.equalTo(cardView.snp.bottom).inset(8)
        }
    }
 
    private func initBackButton()
    {
        view.addSubview(backButton)
        backButton.setTitle("Atras", for: .normal)
        backButton.backgroundColor = UIColor(hex: "#4098C8")
        backButton.addTarget(self, action: #selector(backButtonClicked(sender:)), for: .touchUpInside)
        backButton.snp.makeConstraints { make in
            make.top.equalTo(cardView.snp.bottom).offset(16)
            make.left.equalTo(cardView.snp.left)
        }
    }

    private func initNewTicketButton()
    {
        view.addSubview(newTicketButton)
        newTicketButton.setTitle("Nuevo", for: .normal)
        newTicketButton.backgroundColor = UIColor(hex: "#4098C8")
        newTicketButton.addTarget(self, action: #selector(newTicketButtonClicked(sender:)), for: .touchUpInside)
        newTicketButton.snp.makeConstraints { make in
            make.top.equalTo(cardView.snp.bottom).offset(16)
            make.right.equalTo(cardView.snp.right)
            make.left.equalTo(backButton.snp.right).offset(6)
            make.width.equalTo(backButton.snp.width)
        }
    }
}
Using SnapKit on a real app

SnapKit vs PureLayout vs Cartography vs Mortar

I wanted to learn how to do AutoLayout in code, and I found four libraries (among others) that helps with that. So, I created four different applications, to see the differences between them.

Screenshots:

Comparison:

captura-de-pantalla-2017-02-07-a-las-23-59-27

(image date: Feb-07-2017)

Main screen

SnapKit

box.snp.makeConstraints { make in
    make.top.equalTo(topLayoutGuide.snp.bottom).offset(10)
    make.right.equalTo(view).offset(-10)
    make.bottom.equalTo(view).offset(-10)
    make.left.equalTo(view).offset(10)
}

header.snp.makeConstraints { make in
    make.height.equalTo(box.snp.height).dividedBy(4)
    make.top.equalTo(box.snp.top)
    make.right.equalTo(box.snp.right)
    make.left.equalTo(box.snp.left)
}

logo.snp.makeConstraints { make in
    make.height.equalTo(header.snp.height)
    make.center.equalTo(header.snp.center)
}

button.snp.makeConstraints { make in
    make.height.equalTo(50)
    make.right.equalTo(box.snp.right)
    make.bottom.equalTo(box.snp.bottom)
    make.left.equalTo(box.snp.left)
}

PureLayout

box.autoPin(toTopLayoutGuideOf: self, withInset: 10)
box.autoPinEdge(ALEdge.right, to: ALEdge.right, of: view, withOffset: -10)
box.autoPinEdge(ALEdge.bottom, to: ALEdge.bottom, of: view, withOffset: -10)
box.autoPinEdge(ALEdge.left, to: ALEdge.left, of: view, withOffset: 10)

header.autoMatch(ALDimension.height, to: ALDimension.height, of: box, withMultiplier: 0.25)
header.autoPinEdge(ALEdge.top, to: ALEdge.top, of: box)
header.autoPinEdge(ALEdge.right, to: ALEdge.right, of: box)
header.autoPinEdge(ALEdge.left, to: ALEdge.left, of: box)

logo.autoPinEdge(ALEdge.top, to: ALEdge.top, of: header)
logo.autoPinEdge(ALEdge.bottom, to: ALEdge.bottom, of: header)
logo.autoAlignAxis(ALAxis.horizontal, toSameAxisOf: header)
logo.autoAlignAxis(ALAxis.vertical, toSameAxisOf: header)

button.autoSetDimension(ALDimension.height, toSize: 50)
button.autoPinEdge(ALEdge.right, to: ALEdge.right, of: box)
button.autoPinEdge(ALEdge.bottom, to: ALEdge.bottom, of: box)
button.autoPinEdge(ALEdge.left, to: ALEdge.left, of: box)

Cartography

constrain(view, box) { view, box in
    box.top == topLayoutGuideCartography + 10
    box.right == view.right - 10
    box.bottom == view.bottom - 10
    box.left == view.left + 10
}

constrain(box, header) { box, header in
    header.height == box.height / 4
    header.top == box.top
    header.right == box.right
    header.left == box.left
}

constrain(header, logo) { header, logo in
    logo.height == header.height
    logo.center == header.center
}

constrain(box, button) { box, button in
    button.height == 50
    button.right == box.right
    button.bottom == box.bottom
    button.left == box.left
}

Mortar

box.m_top |=| m_topLayoutGuideBottom + 10
box.m_right |=| view.m_right - 10
box.m_bottom |=| view.m_bottom - 10
box.m_left |=| view.m_left + 10

header.m_height |=| box.m_height / 4
header.m_top |=| box.m_top
header.m_right |=| box.m_right
header.m_left |=| box.m_left

logo.m_height |=| header.m_height
logo.m_center |=| header.m_center

button.m_height |=| 50
button.m_right |=| box.m_right
button.m_bottom |=| box.m_bottom
button.m_left |=| box.m_left

Check the repositories to see more examples, I will be adding some more screens until I decide which one I will use in my next project.

Edit: I ended up using SnapKit, check this: https://melorriaga.wordpress.com/2017/01/26/using-snapkit-on-a-real-app/

Links

https://github.com/matoelorriaga/LearningSnapKit

https://github.com/matoelorriaga/LearningPureLayout

https://github.com/matoelorriaga/LearningCartography

https://github.com/matoelorriaga/LearningMortar

SnapKit vs PureLayout vs Cartography vs Mortar

MySQL: LOAD_FILE returns null

I’m running a web application in a remote Ubuntu machine (using DigitalOcean, which I recommend). I’ve installed MySQL, and everything seems to work OK.

But, at some point I wanted to upload some images inside a blob column of a table, so I needed to use the LOAD_FILE function. But the problem was that it always returns null.

I tried a lot of things:

  • grant FILE permissions to the database user
  • give execution permissions to the folder who contains the images
  • change the owner of the images to mysql
  • etc..

but nothing seems to work..

Finally, I found the solution, and is related with AppArmor.

First approach

Type the following command to see the list of profiles currently loaded:

$ cat /sys/kernel/security/apparmor/profiles

Sample output:

/usr/sbin/tcpdump (enforce)
/usr/sbin/mysqld (enforce)
/usr/lib/connman/scripts/dhclient-script (enforce)
/usr/lib/NetworkManager/nm-dhcp-client.action (enforce)
/sbin/dhclient (enforce)

So, we should disable the mysql profile:

sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld

Now, LOAD_FILE should be working.

To re-enable apparmor protection for mysql again:

sudo rm /etc/apparmor.d/disable/usr.sbin.mysqld
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld

Second approach

Edit the local apparmor security profile for mysql inside /etc/apparmor.d/local directory, and add the directory who contains the images:

nano /etc/apparmor.d/local/usr.sbin.mysqld
melorriaga@ubuntu:/etc/apparmor.d/local$ more usr.sbin.mysqld
# Site-specific additions and overrides for usr.sbin.mysqld.
# For more details, please see /etc/apparmor.d/local/README.
/folder/who/contains/images/** r

Now, LOAD_FILE should be working.

MySQL: LOAD_FILE returns null