Category: Tip

  • Kiểm soát chi phí sử dụng AWS thông qua việc tạo AWS Budget

    Kiểm soát chi phí sử dụng AWS thông qua việc tạo AWS Budget

    Bạn đã trải nghiệm với các dịch vụ trên AWS, nhưng không may bạn nhận được một bất ngờ không mong muốn trên hóa đơn AWS vào cuối tháng?

    Hoặc có lẽ ai đó trong tổ chức của bạn đã hỏi bạn về việc chi phí AWS tăng cao, có cách nào nhận thông báo chi phí đã sử dụng trên AWS theo hằng ngày được không.

    Đây là việc mà có lẽ hầu hết các kĩ sư cloud đã trải qua.

    AWS Budget sẽ giúp các bạn giải quyết các vấn đề đó. Theo dõi các bước bên dưới để thiết lập nó nhé.


    (1) Đăng nhập vào tài khoản AWS của bạn. Nhập AWS Budgets ở ô tìm kiếm. Sau khi hiển thị kết quả chọn AWS Budgets.

    budget1

    (2) Ở màn hình giao diện Budgets, chọn Create budget.

    budget2

    (3) Ở Step 1, chọn Budget types là Cost budget, sau đó ấn Next step.

    budget3

    (4) Ở Step2, nhập các thông tin budget như hình. Ở chỗ nhập Enter your budgeted amount nên nhập giá trị nhỏ, để Budget sẽ dễ dàng kiểm tra và thông báo cho bạn được thường xuyên. Mình hay để chỗ này tầm 5 hoặc 10$.

    budget4

    (5) Ở Step3, chọn Add an alert threadhold để cài đặt alert.

    budget5

    Trên giao diện Alert, mình sẽ cần nhập thông tin như bên dưới:

    • Threshold: Phần trăm cost sử dụng so với giá trị budget đã set ở Step2 để trigger thông báo. Mình hay để chỗ này tầm 50 hoặc 60.
    • Email recipients: Nhập các email bạn mong muốn để nhận thông báo cost. Các email sẽ cách nhau bởi dấu (,)

    budget6 Sau khi đã nhập xong thì mình chuyển sang step tiếp theo.

    (6) Ở Step4, mình sẽ để default không thay đổi gì cả và chuyển sang step tiếp theo. budget7

    (7) Ở Step5, mình sẽ nhìn lại các cài đặt ở các bước lúc trước. Nếu không có vấn đề gì thì mình chọn Create budget. budget8

    (8) Bạn sẽ nhận email có nội dung như bên dưới. budget9

  • CÁCH KHÔNG NHỚ LỆNH GIT MÀ VẪN DÙNG ĐƯỢC GIT NGON Ơ

    CÁCH KHÔNG NHỚ LỆNH GIT MÀ VẪN DÙNG ĐƯỢC GIT NGON Ơ

    TABLE OF CONTENT

    • Định nghĩa về Sourcetree
    • Cài đặt
    • Một số chức năng <br>

    Bạn có phải là một developer?

    Bạn đã từng tương tác với git qua những câu lệnh trên terminal rồi phải không?

    Bạn có bao giờ cảm thấy phát cáu vì bị lẫn lộn hoặc không nhớ được câu lệnh của git không?

    Nếu câu trả lời là không… vậy thì bye bye bạn nhé, bạn không cần phí thời gian để đọc bài viết này đâu.

    Nếu không phải case trên, xin chúc mừng vì tôi có một giải pháp cho bạn đây.

    SOURCETREE

    Sourcetree là một phần mềm giúp cho developer tương tác với git remote repository thông qua giao diện người dùng. Giao diện này thân thiện và dễ hiểu bởi con người hơn so với những dòng lệnh terminal khô khan. Nó thân thiện đến nỗi kể cả một user không phải developer cũng có thể dễ dàng đọc hiểu và tương tác được là bạn phải hiểu như thế nào rồi đấy.

    Xong phần định nghĩa, tiếp theo đi vào 1 trong những chủ đề chính đã được nêu ra ở title đó là

    CÀI ĐẶT SOURCETREE

    Download đã nào: các bạn download phần mền tại link Download SourceTree for Windows – Free – 3.4.5 (digitaltrends.com) hoặc có thể search với từ khóa download Sourcetree để download trên các trang khác.

    Đợi git được install xong thì click next nhé

    Nhập email mà bạn dùng để kết nối với git vào đây rồi click next nhé

    Tại đây click No nhé

    Vậy là việc cài đặt Sourcetree đã hoàn thành rồi. Bây giờ cùng xem cách sử dụng như nào nhé

    SỬ DỤNG SOURCETREE

    Như đã nêu ở phần định nghĩa, Sourcetree là một phần mềm giúp cho developer tương tác với git remote repository thông gia giao diện người dùng. Từ đó suy ra chúng ta phải kết nối được git trên remote repository với repository trên local thì mới sử dụng được. Vậy bây giờ chúng ta sẽ thực hiện đồng thời 2 việc clone project trên remote repository về máy local và connect remote repository với local repository nhé.

    Mở phần mềm Sourcetree lên và chọn tab Clone.

    Source Path / URL: link clone project lấy từ git

    Ví dụ, link source path lấy từ gitlab

    Destination Path: vị trí lưu project sau khi clone về

    Sau khi nhập đầy đủ các thông tin trên bạn click vào button clone

    Sau một vài giây nếu cửa sổ sau được hiển thị thì chúng ta đã cài đặt thành công.

    Phần BRANCHES là những branch trên local repository.

    Phần REMOTES là những branch có trên remote repository.

    Kết nỗi giữa local và remote repository đã xong, giờ đây bạn có thể thực hiện các chức năng thêm, sửa, xóa nhánh, add, commit, push, pull các nhánh và các chức năng khác của git thông qua chiếc tool có giao diện thân thiện này. Sau đây là ví dụ về một số chức năng của Sourcetree.

    1 Lấy nhánh từ remote repository về local repository

    Chuột phải vào tên nhánh trên remote và chọn checkout

    2 Đẩy thay đổi từ local repository lên remote repository

    Mình thử comment 1 dòng code

    Để add thay đổi click vào dấu cộng nhé

    Mở remote repository ra ta có thể thấy thay đổi mà ta vừa đẩy lên

    3 Tạo nhánh mới và đẩy lên remote repository

    Push nhánh vừa tạo lên remote repository (nếu có thay đổi source trên nhánh vừa tạo thì phải thực hiện commit source trước)

    Nhánh mới đã có trên remote repository

    Trên đây là hướng dẫn cơ bản để cài đặt và sử dụng chiếc tool sourcetree thay cho những dòng lệnh khô khan và khó nhớ ở git terminal. Hi vọng rằng bài viết này hữu ích đối với bạn.

  • ANT DESIGN – Điều gì khiến thư viện này vượt “mặt” Material-UI của Google?

    ANT DESIGN – Điều gì khiến thư viện này vượt “mặt” Material-UI của Google?

    anhbia Ant Design – Điều gì khiến thư viện này vượt "mặt" Material-UI của Google?

    Nếu bạn là một Front-End developer và thường xuyên sử dụng những thư viện UI cho các dự án của mình thì chắc chắn các bạn đều sẽ biết đến những cái tên rất quen thuộc như Material-UI (MU) do Google phát triển hay Bootstrap do hai kỹ sư tại Twitter làm ra, tất cả đều là các thư viện hỗ trợ thật sự tuyệt vời về tính Responsive cho các màn hình thiết bị khác nhau, các components Javascript có sẵn.

    Tuy nhiên, bạn biết đó, thời nào cũng sẽ có những anh hùng hào kiệt, một cái tên khác đang phất lên rất nhanh và cũng nhanh chóng được yêu thích của cộng đồng developers React Front-End đó là ANT DESIGN (ANTD) đạt 73,1k sao trên github và đang vượt lên so với Material-UI (70k sao). Có lẽ điều đầu tiên bạn sẽ biết về ANTD đó là thế lực tạo ra nó – Alibaba.

    Vậy ANTD có gì mà có thể vượt lên so với MU – một thư viện UI/UX do Google phát triển rất lâu trước đó?

    Tổng quan bài viết

    ANT DESIGN và ANT DESIGN PRO

    Bạn có phải lần đầu biết đến ANTD? Có khoảnh khắc nào trong những giây phút "chilling" bạn search google với keyword "ant design là gì?" nhưng chắc có lẽ bạn vẫn chưa biết tới cái tên ANT DESIGN PRO nếu chưa bao giờ sử dụng ANTD.

    ANT DESIGNANT DESIGN PRO là hai phần khác nhau của ANTD.

    ANT DESIGN: Dành cho những components đã được viết sẵn, bạn chỉ cần import và tái sử dụng lại những components đó, ví dụ như Carousels, Form, Upload, Modal, Badge,…etc.

    • Điều khiến ANTD trở nên đặc biệt là Document cực kì rõ ràng, các api của components đều có demo và ví dụ đi kèm, bạn có thể xem code trong codeSandbox/codepen/stackblitz. Một số components được ANTD viết với TypeScript, cùng nghía qua một "tí tẹo" document của ANTD nha:

    document

    Nếu những điều này chưa khiến bạn phải "quaoooo" thì hãy xem tới các props của ANTD và khả năng hỗ trợ Customize Base component của nó.

    Bạn biết đấy, đôi khi làm việc với các thư viện, bạn sẽ muốn thay đổi các base components để đáp ứng như cầu của khách hàng/dự án và props của ANTD sẽ là công cụ đắc lực giúp bạn dễ customize hơn và viết code tối giản hơn nhiều.

    Vậy còn ANTD PRO thì sao?

    ANT DESIGN PRO thì khác, nó là một theme gần như hoàn chỉnh dành cho các trang Admin, CMS ( Control Management System). Bạn có thể thấy được những gì mà ANTD PRO có thể hỗ trợ bạn qua <a href="https://preview.pro.ant.design/" target="_blank">website preview</a>.

    • Cài đặt ANTD PRO với npm hoặc yarn:

    install

    Sau khi cài đặt, bạn sẽ thấy rằng điều bạn cần làm là chọn những phần components bạn muốn và bắt đầu gọi API từ phía Back-end hoặc gỉa lập API với file _mock.js có sẵn của ANTD PRO luôn mà không cần phải đau đầu suy nghĩ về UI hay viết code làm sao. Rất tiện lợi đúng không?

    Chưa hết, ANTD PRO còn mang đậm tính "all-in-one", chỉ 1 lần cài đặt mà bạn có thể sử dụng luôn:

    1. Webpack (Webpack giúp tăng tốc độ cho dự án, đóng gói thành một file duy nhất,…)

    2. Less ( một CSS preprocessor giống với SCSS )

    3. Sự tuyệt vời đến từ UmiJS – cái tên bạn … chưa nghe qua bao giờ đúng không?

    UmiJS: chính là một thư viện Plugin enterprise-level cho React. Nghe thì xa tầm với nhưng ý lại nằm trong lời, UmiJS bao hàm bên trong là redux-saga, gọi api thay vì với Axios thì sẽ là Request.

    Ai cũng biết Axios hỗ trợ việc gọi api request tốt và "uy tín" như thế nào nhưng UMI-Request lại hơn thế đấy, cùng xem bảng so sánh của umi-request với Axios và Fetch sau:

    install

    Bạn đã bị thuyết phục chưa? Nếu chưa thì hãy tới mục số II nha.

    WEB COMPONENT vs MOBILE COMPONENT

    Giống như sự hỗ trợ của ANTD dành cho web version thì ANTD cũng đã dành điều đó cho cả <a href="https://mobile.ant.design/docs/react/introduce" target="_blank">mobile version</a>.

    Version dành cho Mobile là cái mà Material UI chưa làm nhưng ANTD đã làm và còn đầu tư chất lượng cho document của mobile không kém cạnh gì cho web version.

    => Có thể thấy ANTD là một thư viện UI được đầu tư, phát triển mạnh mẽ và đến đây chúng ta cũng có thể hiểu tại sao số sao trên github dành cho ANTD lại vượt nhanh hơn cả MU.

    Tips và ví dụ customize sử dụng PROPS của ANTD

    Từ mục I và II chúng ta đều được biết tới sự hỗ trợ "xịn xò" cho các developers React Front-End nhưng lại không biết nhược điểm của nó là gì và tips để sử dụng ANTD hiệu quả đúng không?

    Nhược điểm của ANTD:

    1. Document của ANTD vẫn còn một vài chỗ viết bằng tiếng Trung, đặc biệt là Document của ANTD PRO/UMIJS

    => Việc tiếng Trung còn nhiều khiến cho các developers gặp khó khăn khi muốn customize components hoặc đọc về API/Props hỗ trợ.

    1. Trên các trang như Stackoverflow, dev.to,… có ít các bài được giải thích hoặc hỗ trợ cho ANTD.

    => Vậy nên khi gặp bug, các bạn cũng có thể mất kha khá thời gian để sửa và kiểm tra lại.

    1. Tuỳ chỉnh Theme

    => Khả năng tuỳ chỉnh theme của ANTD không mạnh bằng MU khi chỉ có thể thay đổi các mặc định về màu sắc, kích thước,…

    Tips khi sử dụng ANTD:

    1. Hãy đọc kĩ các phần API, props của ANTD hỗ trợ.

    => Có thể khi đứng trước một vấn đề mà vấn đề đó cần customize một base component của ANTD mà bạn chưa có ý tưởng bắt đầu làm như thế nào, hãy đọc kì api/props mà component đó có, có thể bạn sẽ nhận ra mình đỡ bỏ lỡ một sự kết hợp hay ho đó.

    1. Hãy viết style bằng less

    => ANTD cũng không quá khắt khe cho việc viết style như khi dùng bootstrap nhưng để tối ưu việc style, responsive và sự tương thích với các trình duyệt khác nhau thì hãy viết với less nhé.

    • Ví dụ về customize sử dụng PROPS của ANTD

    Customize dropdown của <Select> với infinity scroll

    Có 5 bước chúng ta cần làm (nguồn: <a href="https://codesandbox.io/s/antd-select-infinite-scroll-90shp?file=/index.js:1649-2578)" target="_blank">click here to open codeSandbox</a>)

    Bước 1: Import các phần cần thiết và viết một component:

    import React from "react";
    import ReactDOM from "react-dom";
    import "antd/dist/antd.css";
    import "./index.css";
    import Select from "antd/lib/select";
    const { Option } = Select;
    
    class LazySelect2Input extends React.PureComponent {
      render() {
        return <div className="App"></div>;
      }
    }
    

    Bước 2: Viết hàm để render mock data:

    // Fetch at most 200
    // On scroll uses a queue
    // Pop
    
    const children = [...Array(20).keys()];
    const range = (start, stop, step) =>
      Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
    const handleChange = (value) => console.log(`selected ${value}`);
    

    Bước 3: Khai báo constructor với props, state:

     constructor(props) {
        super(props);
        this.state = {
          lastScrollPos: 0,
          loadingMore: false,
          options: children,
          min: 0,
          search: undefined
        };
      }
    

    Bước 4: Viết function để thực hiện infinity scroll:

    handleScroll = (event) => {
      event.stopPropagation();
      let delta;
      const { options, min, loadingMore } = this.state;
      let newMin = min;
      if (event.wheelDelta) {
        delta = event.wheelDelta;
      } else {
        delta = -1 * event.deltaY;
      }
    
      if (!loadingMore) {
        if (delta < 0) {
          newMin = min + 1;
          if (newMin + 10 >= Math.max(...options)) {
            // Fetch more items when the list is exhausted.
            this.handleFetchMore(Math.max(...options) + 1, 10, newMin);
          }
        } else if (delta > 0 && min >= 1) {
          newMin = min - 1;
        }
    
        return this.setState({ min: newMin });
      }
    };
    
    handleFetchMore = (offset, limit, min) => {
      this.setState(
        {
          loadingMore: true,
          options: [...this.state.options, ...range(offset, offset + limit, 1)],
        },
        () =>
          this.setState({
            loadingMore: false,
            min,
          })
      );
    };
    

    Bước 5: Render và Return:

    render() {
        const { loadingMore, min, options, search } = this.state;
        let currentOptions = options;
        const extraProps = {};
    
        if (search) {
          currentOptions = options.filter(v => v.toString().indexOf(search) >= 0);
          extraProps.open = true;
          extraProps.key = "searching";
        }
        return (
          <div className="App">
            <Select
              mode="multiple"
              style={{ width: "100%" }}
              onChange={handleChange}
              // dropdownRender, filterOption, defaultActiveFirstOption
              // are props for customize of ANTD
              dropdownRender={menu => <div onWheel={this.handleScroll}>{menu}</div>}
              loading={loadingMore}
              onSearch={search => this.setState({ search })}
              filterOption={false}
              defaultActiveFirstOption={false}
              {...extraProps}
            >
              {currentOptions.slice(min, min + 10).map(i => (
                <Option key={i} value={i}>
                  {i}
                </Option>
              ))}
            </Select>
          </div>
        );
      }
    

    => Có thể thấy các props dropdownRender, filterOption, defaultActiveFirstOption đã hỗ trợ cho chúng ta bắt sự kiện scroll khi tag Select xổ xuống các option để việc thực hiện tải thêm data khi người dùng cuộn xuống phần đáy của tag select (popup container).`

    Tổng kết

    Chúng ta đã có một bài đọc khá dài về ANT DESIGN. Hi vọng những gì mình chia sẻ có thể giúp bạn hiểu thêm về nó và có thể dễ dàng bắt đầu sử dụng hơn nha. Cảm ơn các bạn đã đọc.

  • How to build an end to end scalable visual search system with AI Computer Vision and AWS

    How to build an end to end scalable visual search system with AI Computer Vision and AWS

    With the rise of e-commerce, online retail, visual search is a rapid trend because they are largely driven by visual content. In this article, I will share with you guys the visual search project that was built in 2018 for my Japanese partner.

    What are the problems?

    My partner is one of the biggest retailers of toys, clothing, and baby product in Japan. They have lots of stores all over the world. On holiday, the long queues of customers wait to checkout happen in lots of stores. So to solve this problem, they decide to apply the new store like Amazon Go, no queues, no checkout, just walk out of the store. In short, this not only improves their customers’ buying experience but also maximizes revenue growth.

    Why do we need Visual Search?

    provider

    My partner has lots of providers. Each of them distributes different types of products. The products will be updated frequently by weekly, monthly, or yearly. So visual search system needs to be updated the same as the scale of products.

    What is the core technology here?

    provider

    By the time I started this project in 2018, Triplet loss had proved efficient in Face Recognition. With that starting point, we decide to apply Triplet Loss as our core visual search technology because our problem is quite similar to Face Recognition. To get the highest accuracy and make the system scalable, we classify products into different categories and subcategories because Triplet loss will have the best performance when all the products are in the same domain property. For example, when you do Face Recognition, all the images are facial, right? Then in our problem, we train all the toys that have a similar domain with one AI Model. For example, the Lego toys will be trained together, the Figures will be trained together, and so on. And not only that, one of the most advantages here is when new products need to be updated, our system don’t need to re-training the whole model, the whole process which can reduce the cost. Normally, it’s very costly and takes us lots of time for training an AI model.

    The architecture overview

    provider As you can see, we have two main components: Serving and Training. The training component is built to fit with Admin, Operator, Providers, and Developers with different purposes. For any Machine Learning production, one of the most challenging parts is how your system can run automatically with the minimum of human interference. The serving component will host all APIs needed for our web app and mobile app.

    Triplet Loss training strategy

    Our strategy here to get the best model is: Clustering the categories to group categories with similar type samples. For each anchor image, we will pick 1 hardest positive, 1 hardest negative among the image batch. We will keep multiple anchors and compute the centroid of anchors for each category. To reduce computation cost we will first match with anchor centroids. If the distance is lower than TL and higher than TH, we will give an immediate decision that the product exists and doesn’t exist respectively. We are using two thresholds. Lower threshold, TL, and higher threshold And in the end, our model has very good accuracy, for the trained data, the accuracy is over 99 percent and 97 percent for un-trained data respectively.

    Alt text

    Demo

    A Video Worth a Million words, so, please see this demo below on how we increase customer engagement with AR and AI.

    Alt text

    Conclusion

    This article is focused on sharing the overview flow, architecture, and an example use case when AI is applied in the real world. If you are curious more about the technology, the development, or the business side, please follow my next articles.

  • Git for fun

    Git for fun

    Một vài tips hay ho khi sử dụng git (sử dụng command-line)

    Đối với Backend dev, việc sử dụng command-line tool với git là khá cần thiết vì có thể môi trường làm việc không có sẵn GUI để sử dụng. Đồng thời sử dụng command-line trong nhiều trường hợp mang lại tốc độ tốt hơn so với sử dụng GUI. Vì thế tui cũng hay hướng dẫn mấy dev mới tập quen dần với git command-line

    Git CLI có mấy tips khá là fun, giúp việc sử dụng git trở nên linh hoạt và dễ dàng

    1. Có khi nào bạn muốn commit 1 empty folder lên source code? Việc này là không thể vì git không nhận empty folder. Nhưng tình huống này rất hay xảy ra (trong trường hợp đang base source). Để giải quyết trường hợp này, rất đơn giản, ta tạo 1 file .gitkeep ko có nội dung và cho vào folder đó rồi commit bình thường
    2. Thi thoảng, khi chúng ta có 1 file trót “commit” lên repo, sau đó mới nhận ra file đó nên được cho vào ignore. Nhưng khi thêm file vào .gitignore thì không thấy nhận??? Vậy phải xử lý như thế nào? Rất đơn giản, bạn cần clear cache của git đi sau đó add lại là ok. Cú pháp
    git rm -rf --cached .
    git add . 
    git commit -m "fix ignore"
    1. git checkout develop > git pull origin develop > git checkout -b hotfix_xxxx > git commit -m "message" Các từ develop, checkout... này cứ lặp đi lặp lại. Có cách nào cho đơn giản không? Hãy dùng git alias. Đây là ví dụ sử dụng:
      git cod
      git ci -m “message”
      git co branch
      git lg
      git sync remote_name

    Và đây là nội dung alias tui hay add vào file .gitconfig

    [alias]
        co = checkout
        br = branch
        ci = commit
        st = status
        cod = "checkout develop"
        pod = "pull origin develop"
        lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
        sync = !git checkout develop && git pull $1 develop && git push origin develop && :

    Vụ git lg sẽ cho bạn nhìn git log theo dạng branch-tree với color vô cùng dễ nhìn (nhìn như GUI tool luôn):

    1. Có bao giờ bạn vừa làm việc với git3 của fsoft, vừa làm việc với git server của KH? Làm sao để source code trên máy chỉ cần có 1 bộ source, mà vẫn đảm bảo sync được giữa 2 repo git3 và git private của KH? Đó chính là khái niệm của git remote. Chắc ít người để ý đến cái từ origin khi gõ git fetch origin hay git push origin master nhỉ. Mình đã gặp ở khá nhiều dev mới, quen sử dụng GUI và khi cần sync giữa 2 git server khác nhau, các bạn ấy copy source code và push lên. Wtf??? Đầu tiên phải nắm được 1 số khái niệm cơ bản:
      • origin ở đây hiểu nôm na là 1 cái tên của 1 git server, được đặt ra. Default là origin. Tuy nhiên 1 nước không thể có 2 vua, thế nên nếu làm việc với nhiều git server thì sẽ phải dùng các tên khác. Xem ví dụ cho dễ hiểu nhỉ:

    git remote add origin https://git3.fsoft.com.vn/hotgirlxxx

    git remote add git_customer https://github.com/customer_like_girls

    Vậy là chúng ta đã có 2 remote server khác nhau cùng nằm trong folder source code. Lúc nào cần kéo code từ remote A sync sang remote B thì ta chỉ cần git pull remoteA rồi push sang remoteB là được. Và như file alias tui làm ở trên thì đang quy ước origin là remote name của git fsoft, còn xyz là remote name của git KH. Vậy lúc ae trong team hô “xin em phát anh ơi” thì gõ: git sync xyz => Easy game.



    Một vài tips hay ho hơn nữa sẽ được cung cấp sau khi bài viết đủ 100 likes hehehe…

  • Đừng lạm dụng Enum

    Đừng lạm dụng Enum

    Nhà tâm lý học người Mỹ Abraham Maslow có một câu nói rất nổi tiếng

    If you only have a hammer, you tend to see every problem as a nail. (Nếu dụng cụ duy nhất bạn có chỉ là một chiếc búa, thì mọi vấn đề đều trông giống cái đinh)

    Câu nói này rất phù hợp với lập trình. Mỗi vấn đề đều có nhiều cách tiếp cận với ưu nhược điểm riêng tuỳ theo ngữ cảnh và ràng buộc. Không có giải pháp nào luôn hợp lý hoặc luôn tệ trong tất cả các trường hợp, kể cả Singleton ?. Enum cũng vậy. Nó là một tính năng ngôn ngữ linh hoạt và mạnh mẽ, tuy nhiên việc lạm dụng enum không chỉ làm giảm chất lượng code mà còn khiến codebase khó mở rộng hơn.


    Đầu mục bài viết


    1-Bản chất enum

    Trong tài liệu của mình, Apple chỉ ra rằng enum được tạo ra để định nghĩa một type với mục đích chứa các giá trị liên quan tới nhau. Nói cách khác, hãy dùng enum để nhóm một tập giá trị hữu hạn, cố định, và có quan hệ với nhau. Ví dụ như enum định nghĩa phương hướng

    //Tạo enum Direction ở đây là hợp lý bởi
    //các case liên quan tới nhau và số lượng case là hữu hạn
    enum Direction {
        case north
        case south
        case east
        case west
    }
    

    2-Vấn đề của enum

    Một khi được định nghĩa, chúng ta sẽ không thể thêm case để mở rộng enum mà không làm ảnh hướng tới những chỗ nó được sử dụng. Điều này có thể mang lại lợi ích nếu ta không dùng default case bởi Xcode sẽ giúp tránh việc bỏ lọt code. Tuy nhiên, đây cũng là một nhược điểm lớn trong trường hợp code hiện giờ không quan tâm tới case mới đó.

    Dùng enum để model các Error phức tạp

    Chắc hẳn chúng ta đã từng dùng enum để nhóm các loại Error cho response API như dưới đây

    enum APIError: Error {
        case invalidData(Data)
        case parsingError(Error)
        case noConnection
        case unknown(code: Int, description: String)
        //... các case khác ...
    }
    

    Thoạt nhìn, việc tạo APIError là hợp lý bởi chúng đều là lỗi liên quan đến response API và giờ đây ta có thể gõ .parsingError hay .invalidData cực kì tiện lợi. Mặc dù vậy, hướng tiếp cận này có 2 nhược điểm lớn:

    • Ta không bao giờ muốn switch toàn bộ case của nó
    • Nó không phải là cách tối ưu bởi struct là công cụ tốt hơn để giải quyết bài toán này

    Trong quá trình sử dụng, ngoại trừ các case cần thiết của APIError, việc switch toàn bộ case là hơi thừa thãi. Có thể hiện tại ta chỉ quan tâm đến lỗi .noConnection để hiện alert riêng và các lỗi khác sẽ dùng chung một kiểu alert. Cũng có thể ta chỉ quan tâm đến một vài lỗi nhất định để xử lý logic code nhưng chắc chắn không bao giờ là tất cả case cùng lúc. Lý do là bởi ngoài việc cùng miêu tả các error response, các error trên không có quan hệ gì với nhau.


    Hơn nữa, về mặt logic, việc dùng enum ở đây là sai bởi số lỗi có thể xảy ra khi xử lý API là vô hạn. Điều này trái ngược trực tiếp với bản chất của enum được nhắc đến ở trên. Trong trường hợp này, model APIError bằng struct phù hợp hơn rất nhiều

    struct InvalidData: Error {
        let data: Data
    }
    
    struct ParsingError: Error {
        let underlyingError: Error
    }
    
    struct NoConnection: Error { }
    
    struct Unknown: Error {
        let code: Int
        let description: String
    }
    

    Nếu thực sự muốn nhóm các lỗi vào một kiểu Error, chỉ cần tạo riêng một protocol và conform chúng với protocol đó

    protocol APIError: Error { }
    
    extension InvalidData: APIError { }
    extension ParsingError: APIError { }
    extension NoConnection: APIError { }
    extension Unknown: APIError { }
    

    Việc model APIError bằng structprotocol giúp code linh hoạt hơn khi giờ đây việc tạo ra các Error mới không làm ảnh hưởng đến codebase. Ta cũng có thể cung cấp hàm khởi tạo custom cho chúng, hay conform từng lỗi với các protocol khác nhau một cách dễ dàng thay vì những switch statement cồng kềnh trong enum. Cuối cùng, việc thêm và truy cập biến trong struct đơn giản hơn so với associated value trong enum rất nhiều.


    Sử dụng enum để model các Error đơn giản và hữu hạn là điều nên làm. Tuy nhiên, nếu tập Error đó lớn, hoặc chứa nhiều data đính kèm như các lỗi liên quan đến API thì struct là một lựa chọn tốt hơn hẳn. Trong thực tế, Apple cũng chọn cách này khi tạo URLError xử lý cho Networking của Foundation.

    Dùng enum để config code

    Một sai lầm phổ biến nữa là dùng enum để config UIView, UIViewController, hoặc các object nói chung

    enum MessageCellType {
        case text
        case image
        case join
        case leave
    }
    
    extension MessageCellType {
        var backgroundColor: UIColor {
            switch self {
            case .text: return .blue
            case .image: return .red
            case .join: return .yellow
            case .leave: return .green
            }
        }
        
        var font: UIFont {
            switch self {
            case .text: return .systemFont(ofSize: 16)
            case .image: return .systemFont(ofSize: 14)
            case .join: return .systemFont(ofSize: 12, weight: .bold)
            case .leave: return .systemFont(ofSize: 12, weight: .light)
            }
        }
        
        //...
    }
    
    class TextCell: UITableViewCell {
        func style(with type: MessageCellType) {
            contentView.backgroundColor = type.backgroundColor
            textLabel?.font = type.font
        }
    }
    

    MessageCellType định nghĩa các style cho giao diện của cell ứng với từng loại message để tái sử dụng ở nhiều màn khác nhau. Các thuộc tính chung có thể kể đến như backgroundColor hay UIFont.


    Giống với APIError, vấn đề đầu tiên của MessageCellType là ta không muốn switch toàn bộ case của nó. Với mỗi loại cell, ta chỉ muốn dùng một type nhất định để config cell đó. Việc switch tất cả các case ở hàm cellForRow(at:) là không hợp lý bởi luôn phải trả ra fatalError hoặc một UITableViewCell bù nhìn để thoả mãn Xcode vì số lượng subclass của UITableViewCell là vô hạn ?‍♂️.


    Một vấn đề khác với MessageCellType là việc khó mở rộng. Bản chất của enum là tính hoàn thiện và hữu hạn. Khi thêm bất kì case mới nào, ta đều phải update tất cả các switch statement sử dụng nó. Điều này đặc biệt tệ trong trường hợp đang viết framework vì giờ đây thay đổi sẽ phá hỏng code từ phía client.
    Giải pháp cho MessageCellType là biến nó thành struct và tạo ra các biến static thuộc type này

    struct MessageCellType {
        let backgroundColor: UIColor
        let font: UIFont
    }
    
    extension MessageCellType {
        static let text = MessageCellType(backgroundColor: .blue, font: .systemFont(ofSize: 16))
        static let image = MessageCellType(backgroundColor: .red, font: .systemFont(ofSize: 14))
        static let join = MessageCellType(backgroundColor: .yellow, font: .systemFont(ofSize: 12, weight: .bold))
        static let leave = MessageCellType(backgroundColor: .green, font: .systemFont(ofSize: 12, weight: .light))
    }
    

    Refactor từ enum thành struct giúp việc thêm config mới không còn là vấn đề bởi nó không hề ảnh hưởng tới codebase. Một lợi ích nhỏ nữa là ta vẫn được gõ .join hoặc .leave khi truyền chúng vào trong function

    let cell: TextCell = TextCell()
    cell.style(with: .join)
    

    3-Tổng kết

    Trước khi tạo enum, hãy luôn nhớ rằng

    Enum dùng để switch. Nếu không chắc rằng mình muốn switch nó thì hãy sử dụng struct và protocol


  • GIT – Những lưu ý khi config user name/email cho repo

    GIT – Những lưu ý khi config user name/email cho repo

    Git và các dịch vụ Git như Github, Gitlab đang ngày càng trở nên phổ biến đối với các developer hiện này, hay có thể nói đó là phần không thể thiếu rồi.

    Và đương nhiên, một developer có thể contribute đến nhiều project/repository, và mỗi project có thể dùng một định danh khác nhau. Ví dụ như dùng account email công ty với những project của công ty, hoặc email cá nhân với những project làm thêm, học thêm. Thậm chí có những người được khách hàng cấp account riêng để truy cập vào git riêng của họ.

    Và nếu, developer không quản lý tốt git config cho từng project thì có thể sẽ bị nhầm lẫn email lúc commit lên. Đây là điều rất hay xảy ra nhưng tiếc là ít người để ý.

    Nếu bạn làm trong FSoft, hoặc các công ty đề cao việc security đối với việc dùng email công ty cho các mục đích ngoài công việc, thì có thể nhận được những email warning dạng: bạn đang dùng email công ty trong project git này….hãy cho biết lý do tại sao, và một loạt các câu hỏi khác.

    Tại sao IT lại biết được việc bạn push lên github, gần như ngay lập tức?

    Github có API public các event xảy ra trên dịch vụ này gần như realtime tại: https://api.github.com/events. Và nếu tôi viết 1 con Bot scan liên tục API này và phân tích data thì tôi dễ dàng tìm được thông tin người thực hiện commit đấy lên như email hay user name (đương nhiên là email và user name config thôi)

    Ví dụ như:

    Github public events

    Với sample phía trên, ta dễ dàng scan ra được user name và email của người vừa push lên. Và đương nhiên, con BOT của IT cũng sẽ tóm được ta nếu ta sử dụng email công ty để push lên.

    Tuy nhiên, nhiều người sẽ thắc mắc tại sao với project này, tôi sử dụng email cá nhân để đăng ký, nhưng thông tin push lên lại là email công ty?

    Câu trả lời rất đơn giản, nằm ở chỗ config của git mà thôi.

    Git có 2 loại config chính cho các repo là Global config và Local config. Với những thông tin mà được sử dụng ở tất cả các repo thì thường sẽ config trong Global config. Còn những thông tin riêng của từng project sẽ được config trong Local config của từng dự án.

    Tuy nhiên, rất nhiều developer quên, hoặc không biết đến điều này.

    Cùng xem ví dụ bên dưới.

    Ví dụ về config Git Global trong máy: (open terminal và gõ lệnh git config --list)

    Git global config sample

    Sau khi gõ lệnh thì ta sẽ lấy được tất cả thông tin mà git global config đang setting. (ấn q để thoát ra)

    Ví dụ về 1 project không có config user name và email (dùng lệnh git config --list --local)

    Với project/repo này thì khi commit lên, do thông tin user name và email bị thiếu, nêu git sẽ lấy config default trong Global config để thêm vào. Do đó, nếu bạn đã từng setting email global là email công ty, thì với commit này của bạn, email cũng sẽ là mail công ty, mặc dù repo này bạn không hề đăng ký bằng email công ty. Và IT sờ gáy.

    Vậy, làm thế nào để config thông tin cho từng repo? Ví dụ như dưới

    Cd vào thư mục source, dùng config:

    git config user.name nhathm
    git config user.email [email protected]

    Và như vậy, config đã được áp dụng cho project của bạn, check lại bằng command git config --list --local, nếu 2 dòng cuối cùng hiển thị thông tin đúng như bạn đã config là thành công

    user.name=nhathm
    [email protected]

    Hãy hết sức cẩn thận khi commit nếu như bạn đang tham gia nhiều project khác nhau, sẽ không tốt chút nào nếu repo cá nhân lại bị gắn email công ty, và ngược lại.

    Thanks.

  • Swift – Basic to advanced Closure

    Swift – Basic to advanced Closure

    Với nhiều bài toán, không phải lúc nào cũng đơn giản. Ví dụ chỉ với 2 số nguyên, thực tế có rất nhiều công thức áp dụng được với 2 số này, từ đơn giản như cộng, trừ, nhân, chia … đến phức tạp như hàm mũ, khai căn,… Nếu chỉ sử dụng cách định nghĩa sẵn các function ta không giải quyết tất các các case của bài toán. Giải pháp ở đây là chúng ta sử dụng closure.


    Đầu mục bài viết

    Closure cơ bản

    1. Tạo closure cơ bản
    2. Tạo tham số cho closure
    3. Trả về giá trị từ closure
    4. Truyền closure như 1 tham số vào Function

    Closure nâng cao

    1. Truyền closure với nhiều tham số vào Function
    2. Trả về closure từ functiion
    3. Lưu trữ dữ liệu với closure

    Các kiểu gọi hàm với closure

    Tổng kết


    Basic closure

    1. Tạo closure cơ bản

    Swift cho phép chúng ta sử dụng function như bất kỳ kiểu dữ liệu nào,ví dụ như string, integers,… Điều này có nghĩa rằng chúng ta có thể tạo ra 1 function và gán nó cho một biến, gọi function đó bằng cách sử dụng biến đó và thậm chí có thể gán function đó vào các function khác dưới dạng tham số.

    Function mà ta sử dụng theo cách này được gọi là closure, mặc dù cách hoạt động của nó giống function tuy nhiên cách viết khác nhau 1 chút.

    Ví dụ đơn giản để print 1 đoạn text :

    Ở đây mình tạo ra một function tuy nhiên không có tên, và gán nó cho biến driving. Ta có thể gọi driving() như thể nó là 1 hàm thông thường :

    2. Tạo tham số cho closure

    Khi khởi tạo closure, ta sẽ nhận ra nó không có tên nên sẽ không có bất kỳ vị trí nào để thêm tham số như function bình thường. Tuy nhiên không có nghĩa là closure không nhận tham số input, chỉ là nó có cách làm khác so với function: các tham số được liệt kê bên trong dấu {}.

    Cách tạo ra các tham số để closure có thể “chứa chấp” rất đơn giản, chỉ cần liệt kê chúng bên trong dấu ngoặc đơn ngay sau dấu ngoặc nhọn mở, sau đó thêm keyword in để Swift biết phần nào là phần bắt đầu closure.

    Ví dụ mình sửa driving() bên trên thành closure có chứa tham số

    Và một trong những điểm khác biệt nữa giữa closure và function là bạn không cần sử dụng tên tham số khi gọi closure.

    3. Trả về giá trị từ closure

    Closure có thể trả về giá trị, và cách viết tương tự như khai báo tham số: viết nó trong closure, ngay trước keyword in.

    Với closure driving() bên trên, mình sẽ trả về đoạn string kia thay vì print thẳng ra, để làm thế ta sử dụng → String trước keyword in, sau đó sử dụng return như function bình thường

    Bây giờ có thể gọi closure này và in ra giá trị String nó trả về

    4. Truyền closure như một tham số vào function

    Vì closure có thể được sử dụng như string, integer,…, bạn có thể truyền nó vào 1 function. Syntax của nó khá là rắc rối với newbie tuy nhiên nếu bạn đã hiểu về nó thì sẽ thấy không rắc rối lắm :))

    Đây là closure driving() gốc của chúng ta

    Nếu bạn muốn truyền closure này vào trong 1 function để nó có thể thực thi bên trong function đó, bạn phải chỉ định kiểu tham số là () -> Void.

    Ví dụ mình viết 1 function travel() mà nó nhận tham số là các kiểu action khác nhau

    Ta có thể gọi hàm travel() mà sử dụng closure driving

    Bên trên là 1 ví dụ về truyền closure như 1 tham số, tuy nhiên ta đang sử dụng () → Void, nói cách khác là không truyền vào tham số và cũng không nhận giá trị trả về.

    Tuy nhiên,1 closure vẫn có thể nhận tham số của chính nó khi chính closure đó đang là 1 tham số của function khác.

    Ví dụ, mình viết lại function travel() mà nó chỉ có 1 closure là tham số duy nhất, và closure đó nhận tham số là 1 String

    Và để thực thi function travel(), ta gọi nó với 1 tham số closure


    Advanced closure

    1. Truyền closure với nhiều tham số vào function

    Mình lại xin phép viết lại hàm travel() bên trên, tuy nhiên lần này hàm travel() của chúng ta sẽ cần 1 closure mà nó chứa một vài thông tin khác thay vì 1 string như bên trên, cụ thể nó sẽ chứa thông tin về địa điểm mà một người sẽ đến và tốc độ họ đi. Lúc này chúng ta cần sử dụng (String, Int) → String cho kiểu tham số của closure

    Để thực thi function travel(), ta gọi function này với tham số closure được truyền vào

    ( Bạn có thể thấy lạ với các keyword $0 và $1, hiện tại bạn không hiểu cũng không sao cả vì mình sẽ giải thích rõ hơn ở dưới =]] )

    Closure giống với function là nó có thể nhận bao nhiêu tham số cũng được, tuy nhiên bạn có thể thấy rằng một function mà nhận quá nhiều tham số thì sẽ rất khó hiểu dẫn đến confuse, điều này còn kinh khủng hơn với closure khi bản chất closure cũng đã rất phức tạp rồi. Vì thế để mọi thứ clear bạn nên chỉ sử dụng từ 1 đến 3 tham số  mà thôi.

    2. Trả về closure từ function

    Tương tự như việc bạn truyền tham số closure vào function, bạn cũng có thể nhận về 1 closure mà được trả về từ function.

    Syntax để return closure từ function có hơi rắc rối 1 chút, bởi vì nó dùng → 2 lần: một để chỉ định giá trị trả về của function và một để chỉ định giá trị trả về từ closure.

    Mình lại viết lại hàm travel() mà không nhận tham số, tuy nhiên lại có trả về 1 closure mà closure nhận tham số là String và trả về Void

    Chúng ta gọi travel() để nhận về closure đó, sau đó gọi nó như 1 function

    Còn 1 cách gọi nữa để gọi trực tiếp giá trị trả về từ travel() – cách này không được khuyến khích sử dụng:

    3. Lưu trữ dữ liệu với closure

    Nếu bạn sử dụng bất kỳ giá trị bên ngoài nào trong closure, Swift sẽ lưu và giữ chúng cùng closure, vì thế giá trị này có thể bị thay đổi kể cả nó không còn tồn tại.

    Ví dụ, việc lưu trữ giá trị trong closure xảy ra khi ta tạo 1 giá trị trong hàm travel() mà giá trị đó được sử dụng trong closure, ở đây ví dụ như ta muốn kiểm tra xem closure được gọi trả về bao nhiêu lần

    Mặc dù biến counter được tạo bên trong travel(), nó sẽ được lưu trữ bởi closure vì thế nến nó sẽ vẫn luôn tồn tại cho closure đó.

    Vì thế nếu ta gọi result(“London”) nhiều lần, biến đếm sẽ luôn tăng lên :


    Các kiểu gọi hàm với closure

    Ví dụ, ở đây định nghĩa 1 functiontypecalculationresultcallback với tham số đầu vào là 1 kiểu int và không có giá trị trả về, chúng ta cũng có thêm 1 hàm là multiplyNumber với 2 tham số nhận vào là kiểu int, không có giá trị trả về và kèm theo 1 tham số có tên là callback có kiểu là CalculationResultCallback. Trong ngôn ngữ lập trình nói chung, callback có nghĩa là lời gọi hàm sau, tức là sau quá trình sử lý logic và ra được 1 kết quả nào đó mà chúng ta cần xử lý thêm với kết quả đó.

    Hàm multiplyNumbers có ý nghĩa là, với 2 giá trị nguyên bất kỳ, nó sẽ tính phép nhân 2 số, sau đó sẽ trả về kết quả bằng 1 hàm mà ở đó, ta có thể tùy ý sử dụng kết quả theo ý ta muốn.

    Ta có các cách gọi hàm như sau:

    • Ta có thể thấy, 2 tham số đầu tiên có kiểu int được gọi hoàn toàn bình thường, nhưng với tham số thứ 3 với tên là callback, thay vì truyền vào 1 số, hoặc 1 tên hàm như trước, thì ta truyền vào hẳn 1 hàm,mà ở đó ta in ra dòng “Tích của 2 số là …”
    • Trong closure này,chúng ta có từ khóa in phân cách 2 nửa,bên trái là khai báo tham số nằm trong () và khai báo kiểu trả về là void,bên phải là lệnh được thực thi khi gọi đến closure này,toàn bộ closure được bao trong cặp dấu {}.
    • Giống như function bình thường,ta có thể bỏ void nếu hàm không trả về kết quả,và có thể bỏ đi luôn () bao tham số.
    • Đây chính là dạng gọi cơ bản nhất của closure
    • Swift quy định,với những hàm khai báo closure là tham số nhưng ở vị trí cuối cùng trong danh sách tham số: ví dụ như trên,ta có thể gọi hàm bằng cách gọi hàm với 2 tham số int bình thường,kéo theo 1 tham số closure được bao bằng dấu {},trường hợp này được gọi là Trailing closure
    • Để closure gọn hơn nữa, có 1 tính năng gọi là Shorthands, bản chất là thay vì khai báo tham số với tên biến cụ thể thì chúng ta sử dụng cú pháp $0,$1,$2,… để thay thế cho các tham số ở vị trí 0,1,2,.. Tất nhiên,nếu hàm chỉ có 1 tham số mà ta gọi đến $1 thì hàm sẽ báo lỗi.
    • Đối với shorthands, ta không thể khai báo tham số như những trường hợp trên nữa,đổi lại sẽ ngắn gọn hết sức có thể..
    • Tuy nhiên,với hàm có nhiều tham số,ta khó xác định $0,$1,$2,… là gì.Vì thế nên chỉ sử dụng shorthands với hàm đơn giản chỉ có từ 1-3 tham số mà thôi.

    Tổng kết

    Về bản chất

    • Closure là 1 function.
    • Nhưng là function không đầy đủ tên function và thân function , mà chỉ có mỗi thân function .
    • Mục đích của nó không phải gọi function bằng tên, mà là được chèn vào tham số của 1 function khác.
  • Tips to ask a good question or make a better request

    Tips to ask a good question or make a better request

    Gần đây, mình thấy rất nhiều các bạn trẻ gặp phải các lỗi cơ bản về kỹ năng giao tiếp. Cụ thể là cách đặt câu hỏi/đưa ra yêu cầu trợ giúp. Dẫn đến việc có khá nhiều questions/requests không nhận được câu trả lời, hoặc câu trả lời không đúng như mong muốn của người hỏi.

    Để tránh rơi vào những tình huống như vậy, mình sẽ chỉ cho các bạn một số tips dựa trên kinh nghiệm làm việc với nhiều đối tác, khách hàng cũng như đồng nghiệp trong công ty của mình. Hy vọng sẽ giúp được các bạn phần nào.

    Đầu tiên, bạn cần nắm rõ vấn đề mà mình cần hỏi đã. Hãy tự trả lời những câu hỏi sau trước nhé:

    1. Context/Background (bối cảnh) là gì? Ví dụ:
      • Ai là người phát hiện (detect) ra?
      • Xảy ra ở giai đoạn (phase) nào của dự án?
    2. Problem/Issue (vấn đề) cụ thể là gì?
      • Liên quan đến tính năng (feature/module) nào trong requirements?
      • Tần xuất (frequency) xảy ra là bao nhiêu?
      • Đang hoặc có thể gây ra những ảnh hưởng (impact) gì?
      • Bạn đánh giá mức độ nghiêm trọng (severity) này ra sao (critical, high)?
    3. What have you done/Investigated Actions của bạn là gì rồi?
      • Bạn đã thực hiện tìm kiếm (search/research) thông tin trên Internet chưa?
      • Bạn đã thảo luận với các thành viên có kinh nghiệm hơn trong chính dự án (your team) chưa?
      • Bạn đã thử bao nhiêu phương án (try how many solutions) để giải quyết vấn đề rồi? Cụ thể là gì, kết quả (result) của từng phương án ra sao, đang bị tắc chỗ nào?

    Sau khi đã nắm khá rõ vấn đề và thử một số cách rồi nhưng vẫn chưa ra kết quả, thì mới đi tìm kiếm thêm sự giúp đỡ bạn nhé. Bây giờ, bạn cần xác định thêm:

    1. Right People, người/nhóm cụ thể bạn cần hỏi/tìm kiếm sự trợ giúp là ai? Đừng quăng vấn đề của mình vào 1 group quá lớn hoặc send email tới quá nhiều người mà mong có sự phản hồi nha.
    2. TODO List, những công việc cần làm tiếp theo là gì? Người bạn đang hỏi có thể giúp được gì trong đó?
      • TODO list để giải quyết vấn đề trên có thể là cả tá items, nên bạn cần xác định rõ những items nào bạn và team có thể xoay sở tiếp được, những items nào thực sự vượt quá năng lực/khả năng thì mới nhờ nhé.
    3. Bạn mong muốn nhận được phản hồi hay sự trợ giúp vào lúc nào? Đừng đưa ra một cái expected due time quá gấp, trong trường hợp vấn đề của bạn thực sự urgent thì hãy hỏi xem khi nào người trợ giúp bố trí được thời gian để bạn trình bày trực tiếp với họ nhé.

    Tóm lại, trước khi hỏi hay tìm kiếm sự giúp đỡ, hãy chắc chắn rằng bạn đã hiểu rõ (understand clearly) vấn đề của mình. Và bóc tách (break) vấn đề ra càng nhỏ càng tốt (as small as possible). Khi đó, bạn sẽ rất ngạc nhiên là sao vấn đề to tát của mình nó lại trở nên simple như thế và từ đó bạn có thể ask the Right People the Right Questions.

  • iOS: Slicing Image

    iOS: Slicing Image

    Nội dung bài viết:

    • Tổng quan
    • Cắt ảnh bằng Xcode Slicing Feature
    • Cắt ảnh bằng code.

    Tổng quan

    Trong 1 app iOS, sẽ có những buttons, views,… có kích thước khác nhau nhưng lại có cùng 1 background image.
    Nếu chỉ đơn giản dùng 1 background image cho các buttons, views,… đó thì bạn sẽ gặp phải tình trạng sau:

    Trong trường hợp này, việc sử dụng chung 1 image sẽ làm image bị co/dãn, làm xấu ảnh.
    Vậy nếu bạn chỉ muốn co/dãn phần giữa của ảnh, còn các phần ở 4 góc không bị co/dãn -> Hãy dùng slicing image.

    • Slicing image cho phép bạn có thể cắt 1 bức ảnh theo chiều dọc, chiều ngang hoặc cả ngang và dọc.
    • Nếu cắt theo chiều dọc, bạn sẽ chia bức ảnh làm 3 phần: Left, center và right. Trong trường hợp này, phần left và right là không co/dãn theo chiều ngang, phần center là phần co/dãn theo chiều ngang. (ảnh trên)
    • Nếu cắt theo chiều dọc, bạn sẽ chia bức ảnh làm 3 phần là top, center và bottom. Trong trường hợp này, phần top và bottom là không co/dãn theo chiều dọc, phần center là phần co/dãn theo chiều dọc (ảnh trên).
    • Nếu cắt theo chiều cả ngang và dọc, bạn sẽ chia bức ảnh làm 9 phần như ảnh trên và từng phần sẽ co/dãn như ảnh dưới.

    Cắt ảnh bằng Xcode Slicing Feature:

    1. Chọn vào phần Assets.xcassets trong Xcode.
    2. Chọn ảnh cần cắt.
    3. Chọn Editor -> Show Slicing hoặc chọn nút Show Slicing ở góc dưới bên phải.
    4. Chon Start Slicing
    1. Chọn cắt ảnh theo chiều ngang, chiều dọc hoặc cả ngang và dọc.
    1. Kéo các thanh để xác định phần bên phải, phần bên trái.
    • Phần 1: Phần không bị co/dãn bên trái theo chiều ngang.
    • Phần 2: Phần sẽ bị co/dãn theo chiều ngang.
    • Phần 3: Phần sẽ bị xóa khỏi ảnh mới.
    • Phần 4. Phần không bị co/dãn bên phải theo chiều ngang.
    Kết qủa: Ảnh bị co dãn phần ở giữa nhưng 2 bên không bị co dãn -> ảnh không có cảm giác bị méo

    Cắt ảnh bằng code:

    Bạn tạo ra 1 ảnh mới bằng cách cắt 1 ảnh có sẵn bằng hàm: resizableImage(withCapInsets:) hoặc resizableImage(withCapInsets:resizingMode:)

    if let img = UIImage(named: "button") {
        let resizable = img.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24), resizingMode: .stretch)
    }
    • Khai báo top và bottom # 0 nếu bạn muốn cắt ảnh theo chiều ngang.
    • Khai báo left và right # 0 nếu bạn muốn cắt ảnh theo chiều dọc.
    • Khai báo top, left, right và bottom # 0 nếu bạn muốn cắt ảnh theo cả ngang và dọc.

    Hi vọng qua bài viết này, bạn sẽ không gặp khó khăn khi apply 1 ảnh cho các buttons, views có size khác nhau.

    Link tham khảo: https://developer.apple.com/documentation/uikit/uiimage