After the classmate’s suggestion, found that the writing is really a little messy, take advantage of the time before going to work to tidy up the module

Recently, taking advantage of the project demand is not very tight, I used the spare time to complete the function to write a best project into swift version. I have to say, SWIFT version drives me crazy, the strict grammar structure is always broken, it is almost killing me. I didn’t learn Swift for a long time. I read some basic knowledge points in bits and pieces, and found that reading documents was boring, so I tried to rewrite the existing project by myself. The process was painful and the harvest was really great.

Here I mainly through the essence, release, attention, login, my and recommended attention interface for a few modules of the simple implementation of the introduction, because the code has been knocking for a year, language ability has been degraded, the expression is not good directly on the code, hey hey ~~~ ~

The overall framework construction of the project

Written all know of the project, the most commonly used is the framework of TabBarController + NavigationController form, here is the classic style, still used concrete implementation and OC is same, also includes the custom tabbar, show part of the code on the following:

Block of code:

    private func setupChildVcs() {
    
        setupChildVc(vc: EssenseController(),title:"Essence",image:"tabBar_essence_icon",selectedImage:"tabBar_essence_click_icon")
        setupChildVc(vc: NewViewController(),title:"New post",image:"tabBar_new_icon",selectedImage:"tabBar_new_click_icon")
        setupChildVc(vc: FriendThrendController(),title:"Attention",image:"tabBar_friendTrends_icon",selectedImage:"tabBar_friendTrends_click_icon")
        setupChildVc(vc: ProfileController(),title:"我",image:"tabBar_me_icon",selectedImage:"tabBar_me_click_icon")

    }
    
    
    private func setupTabBar() {
        setValue(TabBar(), forKeyPath: "tabBar");
        
    }
    
    private func setupChildVc(vc:UIViewController,title:String,image:String,selectedImage:String) {
    
        let nav = NavigationController(rootViewController: vc)
        
        addChildViewController(nav)
        
        nav.tabBarItem.title = title
        
        nav.tabBarItem.image = UIImage(named: image)
        
        nav.tabBarItem.selectedImage = UIImage(named: selectedImage)
        
        
    }
Copy the code

This is mainly about building the overall framework. One thing I think is quite good is adding a plus button to the custom Tabbar

override func layoutSubviews() { super.layoutSubviews() publishButton? .center = CGPoint(x: width * 0.5, y: height * 0.5)let buttonY:CGFloat = 0
        let buttonW = width / 5
        let buttonH = height
        var index:Int = 0
        var buttonX:CGFloat = 0

        for  button in subviews {
            if! button.isKind(of: NSClassFromString("UITabBarButton")!){
                continue
            }            
            buttonX = buttonW * CGFloat((index > 1 ? index + 1 : index))
            button.frame = CGRect(x: buttonX, y: buttonY, width: buttonW, height: buttonH)
            index += 1
        }
        
        publishButton?.addTarget(self, action: #selector(TabBar.publishButtonClick), for: .touchUpInside)
        
    }
Copy the code

But in the process of writing is more helpless pain, different types of constants in swift is not for arithmetic, must be converted to the same type can be, to this point in the process of writing is not here right now, but fortunately the Xcode directly error warning, can timely correction, so when it comes to this, I want to say is, write down to complete a small project, I learned almost as I wrote, and swift’s grammatical rigor was maddening and exhilarating

The overall framework is introduced, and the following modules are introduced:

2. Essence/new post module

1. Network request and data analysis

This module is mainly to show the list, the form of the list includes video, sound, pictures and the mixture of paragraphs, so it needs to customize the cell, the realization form here is tableView, but there is no reuse of tableView, at this point, if you want to save memory, you can reuse two or three TableViews. Here do not introduce more, there are relevant learning materials on the net, do not understand can go to study.

Since most network requests are the same, I use a common class TopViewController for the list layout

extension TopViewController {

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return viewModel.topicArray.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell  = tableView.dequeueReusableCell(withIdentifier: "topic".for: indexPath) as! TopicCell
        
        cell.topicModel = viewModel.topicArray[indexPath.row] as! TopicModel
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        let topicModel = viewModel.topicArray[indexPath.row] as! TopicModel
        
        return topicModel.cellHeight
    }
}
Copy the code

I put the network request in a class called TopicViewModel, and the controller just passes in the page argument and the tableView, and nothing else

func loadTopicModel() {
        viewModel.topicArray.removeAllObjects()
        viewModel.page = 0
        viewModel.loadTopicDataFromNet()
    }
    
    func loadMoreTopicModel() {
        viewModel.page += 1
        tableView.mj_footer.beginRefreshing()
        viewModel.loadTopicDataFromNet()
    }
Copy the code

This technique and I search on the net of some great god to MVVM demo some like, but I think this is not a real MVVM, just made some thin body, the controller should remain essentially belongs to the MVC, in recommended attention on this in the module I will roughly said about the concrete method of thin body, there is directly on the code snippet:

func loadTopicDataFromNet() {
     
        var parameters = [String:Any]()
        
        parameters["a"] = a
        
        parameters["c"] = "data"
        
        parameters["type"] = type
        
        parameters["page"] = page
        
        ifmaxtime ! = nil && (maxtime? .characters.count)! > 0 { parameters["maxtime"] = maxtime
        }
        self.parameters = parameters as NSDictionary?
        
        NetWorkTool.NetWorkToolGet(urlString: "http://api.budejie.com/api/api_open.php", parameters: parameters, success: { (responseObj) in
            
            ifself.parameters ! = (parameters as NSDictionary?) {return} self.maxtime = (responseObj? ["info"] as! [String:Any])["maxtime"] as! String?
            
            var array = [Any]()
            for topicModel inTopicModel.mj_objectArray(withKeyValuesArray: responseObj! ["list"]) {
            array.append(topicModel)
            }
            
            self.topicArray.addObjects(from: array)
            self.tableView.reloadData()
            self.tableView.mj_header.endRefreshing()
            self.tableView.mj_footer.endRefreshing()
            
            }) { (error) in
                ifself.parameters ! = (parameters as NSDictionary?) {return
                }
                self.tableView.mj_header.endRefreshing()
                self.tableView.mj_footer.endRefreshing()
                if self.page > 0 {
                
                    self.page -= 1
                }
        }
        
    }
Copy the code

AFN is adopted for network request. At the beginning, I wanted to use the framework Alamofire to write, but it was not very easy to use, so I still used the commonly used AFN, and MJExtension was used for data parsing. The method is similar to OC, but Swift has strict requirements on data type. In the use of time need to pay more attention, according to the error of the corresponding modification, especially about the optional type and closure, it is recommended that you can thoroughly understand before writing, of course, can also understand while writing, I belong to the latter

2. Cell customization

Cell custom, mainly USES is combination of xib and code, the xib’s really convenient to use, to avoid a lot of the writing of the code, specific implementation can have a look at the download code, only on a few picture to display here, another thing to say is that the height of the cell, I was on the calculation and processing of model inside, this point, I didn’t know where to start when I first tried to do it this way, and then I asked the kid who wrote it, and he told me, and it’s like this, mainly didSet, because Swift, unlike OC, doesn’t have setter methods

var text:String? {
        
        didSet {
            ifCellHeight = = {0.0 cellHeight = ConstTool. Instance. TopicTextYlet maxSize = CGSize(width: ConstTool.instance.kScreenW - 4 * ConstTool.instance.TopicMargin, height: CGFloat(MAXFLOAT))
                let textStr = NSString(string: text!)
                let textH = textStr.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 14)], context: nil).size.height
                cellHeight += textH + ConstTool.instance.TopicMargin
                
                let contentW = maxSize.width
                var contentH = contentW * height / width
                let contentX = ConstTool.instance.TopicMargin
                let contentY = cellHeight
                if type= = {10if contentH > ConstTool.instance.TopicPictureMaxH {
                        contentH = ConstTool.instance.TopicPictureDefaultH
                        hiddenSeebigButton = true
                    }else {
                        hiddenSeebigButton = false
                    }
                    pictureF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
                    cellHeight += contentH + ConstTool.instance.TopicMargin
                }else if (type == 31) {
                    
                    voiceF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
                    cellHeight += contentH + ConstTool.instance.TopicMargin
                }else if(type == 41) {
                    
                    videoF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
                    cellHeight += contentH + ConstTool.instance.TopicMargin
                }
                
                iftop_cmt ! = nil {let commentContent = NSString(format: "% @ : % @", (top_cmt? .user? .username)! ,(top_cmt? .content)!)let commentContentH = commentContent.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 13)], context: nil).size.height
                    cellHeight += commentContentH + ConstTool.instance.TopicMargin
                }
                
                cellHeight += ConstTool.instance.TopicBottomH + ConstTool.instance.TopicMargin
            }   
        }   
    }
Copy the code

This can be understood in terms of Setter methods for OC

Here are some screenshots:

Click the plus button to create an animated display of the release interface. This effect looks cool, but it is not too difficult to implement, using a third-party library called POP

The main code is as follows:

    func setupButtons() {
        let maxCols = 3
        let buttonW = 72
        let buttonH = buttonW + 30
        let buttonStartX = 15
        let buttonMargx = (ConstTool.instance.kScreenW - CGFloat(maxCols * buttonW) - 2 * CGFloat(buttonStartX)) / CGFloat(maxCols - 1)
        let buttonStartY = (ConstTool.instance.kScreenH - 2 * CGFloat(buttonH)) / CGFloat(2)
        for  i in 0 ..< imagesArr.count {
            let button = VerticalButton(type: .custom)
            let row = i / maxCols
            let col = i % maxCols
            let buttonX = CGFloat(buttonStartX) + (buttonMargx + CGFloat(buttonW)) * CGFloat(col)
            
            let buttonEndY = buttonStartY + CGFloat(row) * CGFloat(buttonH)
            button.setImage(UIImage(named:imagesArr[i]), for: .normal)
            button.setTitle(titlesArr[i], for: .normal)
            button.setTitleColor(UIColor.black, for: .normal) button.titleLabel? .font = UIFont.systemFont(ofSize: 12) addSubview(button) button.addTarget(self, action:#selector(PublishView.clickButton(button:)), for: .touchUpInside)
            
            button.tag = i
            letanimation = POPSpringAnimation(propertyNamed: kPOPViewFrame) animation? .fromValue = NSValue.init(cgRect: CGRect(x: buttonX, y: CGFloat(buttonH) - ConstTool.instance.kScreenH, width: CGFloat(buttonW), height: CGFloat(buttonH))) animation?.toValue = NSValue.init(cgRect: CGRect(x: buttonX, y: CGFloat(buttonEndY), width: CGFloat(buttonW), height: CGFloat(buttonH))) animation?.springBounciness = 5 animation?.springSpeed = 15 animation?.beginTime = CACurrentMediaTime() + CFTimeInterval(0.1 * CGFloat(I)) button.pop_add(animation,forKey: nil)
        }
        
        sloganView = UIImageView(image: UIImage(named: "app_slogan"))
        
        addSubview(sloganView)
        
        letCenterX: CGFloat = ConstTool. Instance. KScreenW * 0.5letCenterEndY: CGFloat = ConstTool. Instance. KScreenH * 0.2let centerBeginY:CGFloat = centerEndY - ConstTool.instance.kScreenH
        let animation = POPSpringAnimation(propertyNamed: kPOPViewCenter)
        animation?.fromValue = NSValue.init(cgPoint: CGPoint(x: centerX, y: centerBeginY))
        animation?.toValue = NSValue.init(cgPoint: CGPoint(x: centerX, y: centerEndY))
        animation?.springBounciness = 5
        animation?.springSpeed = 15
        animation?.beginTime = CACurrentMediaTime() + CFTimeInterval(0.1 * CGFloat(imagesArr.count))
        animation?.completionBlock = { (animation,finished) in
        
        self.isUserInteractionEnabled = true
        }
        sloganView.pop_add(animation, forKey: nil)
    }
Copy the code

Interested colleagues can download the source code to take a closer look at Pop, which is available on Github

# 4 Login module

This module does not implement the login function, just the layout of the login and registration interface, mainly using the XIB layout

Click the register account to animate the register interface, mainly by changing the constraints on the left side of the login interface

    @IBAction func shouRegisterView(_ sender: UIButton) {
        if leftConstraint.constant == 0 {
            leftConstraint.constant = -view.width
            sender.isSelected = true
        }else {
            leftConstraint.constant = 0
            sender.isSelected = falseAnimate (withDuration: 0.3, animations: animation)} funcanimation() {
        self.view.layoutIfNeeded()
    }
Copy the code

Five recommended attention modules

In this module I want to show you the implementation of pulling out the tableView, which is very possible to slim down the controller

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        navigationItem.title = "Recommended attention"
        view.backgroundColor = ConstTool.instance.GlobalColor()
        automaticallyAdjustsScrollViewInsets = false
        
        categoryView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
        categoryListView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
     
        categoryView.delegate = self.manager
        categoryListView.delegate = self.manager
        
        categoryListView.dataSource = self.manager
        categoryView.dataSource = self.manager
        
        self.viewModel.categoryView = categoryView
        self.viewModel.categoryListView = categoryListView
        
        self.manager.viewModel = self.viewModel
        
        
        self.viewModel.getRecommendCategoryDataFromNet()
        
    }
Copy the code

These are the only lines of code I recommend watching your controller. Isn’t that refreshing? I’m also pretty cool that I’ve pulled out the tableView’s delegate method and datasource method and put them in a Manager class that inherits from NSObject, and set the tableView’s delegate and datasource to Manager

In the Manager, you associate a class that manages the data, called the viewModel, with the data source and UI interaction relying on these two classes. The controller can be almost completely isolated, which reduces coupling and makes it easy to modify

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
        if tableView.tag == 1 {
            
            return viewModel.categoryArray.count
        }
        tableView.mj_footer.isHidden = (viewModel.categoryListArray.count == 0)
        return viewModel.categoryListArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if tableView.tag == 1 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell".for: indexPath) as! CategoryCell
            cell.categoryModel = viewModel.categoryArray[indexPath.row] as! CategoryModel
            
            return cell
        }
        
        let cell  = tableView.dequeueReusableCell(withIdentifier: "categoryListCell".for: indexPath) as! CategoryListCell
        cell.categoryListModel = viewModel.categoryListArray[indexPath.row] as! CategoryListModel
        return cell
        
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        if tableView.tag == 2 {
            return80}return 44
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        if tableView.tag == 1 {
            viewModel.categoryListView.mj_footer.endRefreshing()
            let model = viewModel.categoryArray[indexPath.row] as! CategoryModel
            viewModel.page = 1
            viewModel.getRecommendCategoryListDataFromNetWithCategoryId(ID: model.ID)
            
        }
    }
Copy the code

The viewModel is another class, as follows:

    func getRecommendCategoryDataFromNet() {
        
        SVProgressHUD.show()
        
        var parameters = [String:Any]()
        
        parameters["a"] = "category"
        parameters["c"] = "subscribe"
        let manager = AFHTTPSessionManager()
        
         manager.get("http://api.budejie.com/api/api_open.php", parameters: parameters, progress: {(progress) in
        
        }, success: { (task, responseObj)  in
        SVProgressHUD.dismiss()
            ifresponseObj ! = nil {let response = responseObj as! [String:Any]
                
                self.categoryArray = CategoryModel.mj_objectArray(withKeyValuesArray: response["list"])
                
                self.categoryView.reloadData()
                
                let indexPath = NSIndexPath(row: 0, section: 0)
                
                self.categoryView.selectRow(at: indexPath as IndexPath, animated: false, scrollPosition: .top)
                let model = self.categoryArray[0] as! CategoryModel
                
                self.getRecommendCategoryListDataFromNetWithCategoryId(ID: model.ID)
                
            }
            
        }, failure: {(task, error )  in
        
            SVProgressHUD.showError(withStatus: "Failed to load classified data")})}Copy the code

When the data comes back, you just need tableView.reloadData() to change the layout

The specific implementation is like this, but if the processing is more complex logic, this may not be so simple, I feel more suitable for tableView to show the type of data, this way can try more

This module is relatively simple, I will not do more description here, see the demo for details, attached below a picture:

I have uploaded the specific demo to gitHub, and interested colleagues can download it. Because it is the first time to write code with Swift, many aspects are not in place and the function implementation is relatively simple. If there is a chance later, I will improve the function processing:

Swift version best not sister link address